From f49177f0b71b745e96423d390db8562d85e12974 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Fri, 25 Jul 2014 18:46:29 -0700 Subject: [PATCH 001/178] paypro: begin adding code for payment protocol. --- js/controllers/send.js | 3 +- js/models/core/TxProposals.js | 1 - js/models/core/Wallet.js | 273 ++++++++++++++++++++++++++++++++++ 3 files changed, 275 insertions(+), 2 deletions(-) diff --git a/js/controllers/send.js b/js/controllers/send.js index dbf98b6aa..3b1568382 100644 --- a/js/controllers/send.js +++ b/js/controllers/send.js @@ -68,9 +68,10 @@ angular.module('copayApp.controllers').controller('SendController', notification.success('Success!', message); $scope.loadTxs(); } else { - w.sendTx(ntxid, function(txid) { + w.sendTx(ntxid, function(txid, ca) { if (txid) { notification.success('Transaction broadcast', 'Transaction id: ' + txid); + if (ca) notification.success('Root Certificate', ca); } else { notification.error('Error', 'There was an error sending the transaction.'); } diff --git a/js/models/core/TxProposals.js b/js/models/core/TxProposals.js index f0933afd6..558d9c501 100644 --- a/js/models/core/TxProposals.js +++ b/js/models/core/TxProposals.js @@ -11,7 +11,6 @@ var Key = bitcore.Key; var buffertools = bitcore.buffertools; var preconditions = require('preconditions').instance(); - function TxProposals(opts) { opts = opts || {}; this.walletId = opts.walletId; diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index 015b46ad9..0e14b3235 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -14,6 +14,8 @@ var Builder = bitcore.TransactionBuilder; var SecureRandom = bitcore.SecureRandom; var Base58Check = bitcore.Base58.base58Check; var Address = bitcore.Address; +var PayPro = bitcore.PayPro; +var Transaction = bitcore.Transaction; var HDParams = require('./HDParams'); var PublicKeyRing = require('./PublicKeyRing'); @@ -730,6 +732,12 @@ Wallet.prototype.sign = function(ntxid, cb) { Wallet.prototype.sendTx = function(ntxid, cb) { var txp = this.txProposals.get(ntxid); + + if (txp.merchant) { + return this.sendTxToMerchant(ntxid, + { uri: txp.merchant, txp: txp }, cb); + } + var tx = txp.builder.build(); if (!tx.isComplete()) throw new Error('Tx is not complete. Can not broadcast'); @@ -760,6 +768,271 @@ Wallet.prototype.sendTx = function(ntxid, cb) { }); }; +// NOTE: +// This process is currently backwards. This is really awkward to handle +// because the tx outputs are given to us by the server in the payment details. +// This is just preliminary code, not meant to be used. + +Wallet.prototype.sendTxToMerchant = function(ntxid, options, cb) { + var self = this; + + if (typeof options === 'string') { + options = { uri: options }; + } + + var txp = this.txProposals.txps[ntxid]; + if (!txp) return; + + var tx = txp.builder.build(); + if (!tx.isComplete()) return; + this.log('Sending transaction to merchant'); + + tx = tx.serialize(); + + var txHex = tx.toString('hex'); + this.log('Raw transaction: ', txHex); + + return $http({ + method: options.method || 'POST', + url: options.uri || options.url, + headers: { + 'Accept': PayPro.PAYMENT_REQUEST_CONTENT_TYPE + + ', ' + PayPro.PAYMENT_ACK_CONTENT_TYPE, + 'Content-Type': 'application/octet-stream', + 'Content-Length': 0 + }, + responseType: 'arraybuffer' + }) + .success(function(data, status, headers, config) { + data = PayPro.PaymentRequest.decode(data); + var pr = new PayPro(); + pr = pr.makePaymentRequest(data); + return self._receivePaymentRequest(tx, options, pr, cb); + }) + .error(function(data, status, headers, config) { + return cb(new Error('Status: ' + JSON.stringify(status))); + }); +}; + +Wallet.prototype._receivePaymentRequest = function(tx, options, pr, cb) { + var self = this; + + 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 trusted = certs.every(function(cert) { + var der = cert.toString('hex'); + var pem = PayPro.prototype._DERtoPEM(der, 'CERTIFICATE'); + return RootCerts.getTrusted(pem); + }); + + if (!trusted.length) { + return cb(new Error('Not a trusted certificate.')); + } + + // Verify Signature + var verified = pr.verify(); + + if (!verified) { + return cb(new Error('Server sent a bad signature.')); + } + + options.ca = trusted[0]; + + 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'); + + return this._createPaymentTx(outputs, function(err, tx) { + if (err) return cb(err); + + self.log('You are currently on this BTC network:'); + self.log(network); + self.log('The server sent you a message:'); + self.log(memo); + + var refund_outputs = []; + + options.refund_to = options.refund_to || self.getAddresses()[0]; + + if (options.refund_to) { + var rpo = new PayPro(); + rpo = rpo.makeOutput(); + rpo.set('amount', 0); + rpo.set('script', + Buffer.concat( + new Buffer([ + 118, // OP_DUP + 169, // OP_HASH160 + 76, // OP_PUSHDATA1 + 20, // number of bytes + ]), + options.refund_to, + new Buffer([ + 136, // OP_EQUALVERIFY + 172 // OP_CHECKSIG + ]) + ) + ); + refund_outputs.push(rpo.message); + } + + // We send this to the serve after receiving a PaymentRequest + var pay = new PayPro(); + pay = pay.makePayment(); + pay.set('merchant_data', merchant_data); + pay.set('transactions', [tx.serialize()]); + pay.set('refund_to', refund_outputs); + + options.memo = options.memo || options.comment + || 'Hi server, I would like to give you some money.'; + + pay.set('memo', options.memo); + + options.payment_url = options.payment_url || payment_url; + + return self._sendPayment(tx, options, pay, cb); + }); +}; + +Wallet.prototype._sendPayment = function(tx, options, pay, cb) { + var self = this; + return $http({ + method: 'POST', + url: options.payment_url, + headers: { + // BIP-71 + 'Accept': PayPro.PAYMENT_REQUEST_CONTENT_TYPE + + ', ' + PayPro.PAYMENT_ACK_CONTENT_TYPE, + 'Content-Type': 'application/bitcoin-payment', + 'Content-Length': pay.length + '', + 'Content-Transfer-Encoding': 'binary' + }, + body: pay.serialize(), + responseType: 'arraybuffer' + }) + .success(function(data, status, headers, config) { + if (err) return cb(err); + data = PayPro.PaymentACK.decode(data); + var ack = new PayPro(); + ack = ack.makePaymentACK(data); + return self._receivePaymentRequestACK(tx, options, ack, cb); + }) + .error(function(data, status, headers, config) { + return cb(new Error('Status: ' + JSON.stringify(status))); + }); +}; + +Wallet.prototype._receivePaymentRequestACK = function(tx, options, ack, cb) { + var self = this; + var payment = ack.get('payment'); + var memo = ack.get('memo'); + this.log('Our payment was acknowledged!'); + this.log('Message from Merchant: %s', memo); + payment = PayPro.Payment.decode(payment); + var pay = new PayPro(); + payment = pay.makePayment(payment); + var tx = payment.message.transactions[0]; + if (tx.buffer) { + tx.buffer = tx.buffer.slice(tx.offset, tx.limit); + var ptx = new bitcore.Transaction(); + ptx.parse(tx.buffer); + tx = ptx; + } + var txid = tx.getHash().toString('hex'); + return cb(txid, ca); +}; + +Wallet.prototype._createPaymentTx = function(outputs, cb) { + var self = this; + return this.getUnspent(function(err, unspent) { + if (err) return cb(err); + + var outs = []; + outputs.forEach(function(output) { + outs.push({ + address: self.getAddressesStr()[0], // dummy address + amount: 0 // dummy value + }); + }); + + var opts = { + remainderOut: { + address: self._doGenerateAddress(true).toString() + } + }; + + var tx = new Builder(opts) + .setUnspent(unspent) + .setOutputs(outs); + + var priv = self.privateKey; + var pkr = self.publicKeyRing; + var selectedUtxos = tx.getSelectedUnspent(); + var inputChainPaths = selectedUtxos.map(function(utxo) { + return pkr.pathForAddress(utxo.address); + }); + if (priv) { + var keys = priv.getForPaths(inputChainPaths); + var signed = tx.sign(keys); + } + + tx = tx.sign(keys); + tx = tx.build(); + + outputs.forEach(function(output, i) { + var value = output.get('amount'); + var script = output.get('script'); + var v = new Buffer(8); + v[0] = (value.low >> 0) & 0xff; + v[1] = (value.low >> 8) & 0xff; + v[2] = (value.low >> 16) & 0xff; + v[3] = (value.low >> 24) & 0xff; + v[4] = (value.high >> 0) & 0xff; + v[5] = (value.high >> 8) & 0xff; + v[6] = (value.high >> 16) & 0xff; + v[7] = (value.high >> 24) & 0xff; + var s = script.buffer.slice(script.offset, script.limit); + tx.outs[i].v = v; + tx.outs[i].s = s; + }); + + self.log(''); + self.log('Created transaction:'); + self.log(tx.getStandardizedObject()); + self.log(''); + + return cb(null, tx); + }); +}; + +Wallet.prototype.addSeenToTxProposals = function() { + var ret = false; + var myId = this.getMyCopayerId(); + + for (var k in this.txProposals.txps) { + var txp = this.txProposals.txps[k]; + if (!txp.seenBy[myId]) { + + txp.seenBy[myId] = Date.now(); + ret = true; + } + } + return ret; +}; // TODO: remove this method and use getAddressesInfo everywhere Wallet.prototype.getAddresses = function(opts) { From 6c098030c3273ae8d2fb48610f055b5805cc85e4 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Sun, 27 Jul 2014 21:39:13 -0700 Subject: [PATCH 002/178] paypro: fix trusted check. --- js/models/core/Wallet.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index 0e14b3235..5e0cfb43a 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -832,7 +832,7 @@ Wallet.prototype._receivePaymentRequest = function(tx, options, pr, cb) { return RootCerts.getTrusted(pem); }); - if (!trusted.length) { + if (!trusted) { return cb(new Error('Not a trusted certificate.')); } From ebf31379466a121e78ebcfdecbe8acaf7d95876b Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Sun, 27 Jul 2014 22:56:57 -0700 Subject: [PATCH 003/178] paypro: rework flow of payment protocol. --- js/models/core/Wallet.js | 268 +++++++++++++++++++++------------------ 1 file changed, 146 insertions(+), 122 deletions(-) diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index 5e0cfb43a..efb01d057 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -733,10 +733,10 @@ Wallet.prototype.sign = function(ntxid, cb) { Wallet.prototype.sendTx = function(ntxid, cb) { var txp = this.txProposals.get(ntxid); - if (txp.merchant) { - return this.sendTxToMerchant(ntxid, - { uri: txp.merchant, txp: txp }, cb); - } + // if (txp.merchant) { + // return this.createPaymentTx(ntxid, + // { uri: txp.merchant, txp: txp }, cb); + // } var tx = txp.builder.build(); if (!tx.isComplete()) @@ -773,25 +773,13 @@ Wallet.prototype.sendTx = function(ntxid, cb) { // because the tx outputs are given to us by the server in the payment details. // This is just preliminary code, not meant to be used. -Wallet.prototype.sendTxToMerchant = function(ntxid, options, cb) { +Wallet.prototype.createPaymentTx = function(ntxid, options, cb) { var self = this; if (typeof options === 'string') { options = { uri: options }; } - var txp = this.txProposals.txps[ntxid]; - if (!txp) return; - - var tx = txp.builder.build(); - if (!tx.isComplete()) return; - this.log('Sending transaction to merchant'); - - tx = tx.serialize(); - - var txHex = tx.toString('hex'); - this.log('Raw transaction: ', txHex); - return $http({ method: options.method || 'POST', url: options.uri || options.url, @@ -807,14 +795,14 @@ Wallet.prototype.sendTxToMerchant = function(ntxid, options, cb) { data = PayPro.PaymentRequest.decode(data); var pr = new PayPro(); pr = pr.makePaymentRequest(data); - return self._receivePaymentRequest(tx, options, pr, cb); + return self.receivePaymentRequest(tx, options, pr, cb); }) .error(function(data, status, headers, config) { return cb(new Error('Status: ' + JSON.stringify(status))); }); }; -Wallet.prototype._receivePaymentRequest = function(tx, options, pr, cb) { +Wallet.prototype.receivePaymentRequest = function(tx, options, pr, cb) { var self = this; var ver = pr.get('payment_details_version'); @@ -856,60 +844,78 @@ Wallet.prototype._receivePaymentRequest = function(tx, options, pr, cb) { var payment_url = pd.get('payment_url'); var merchant_data = pd.get('merchant_data'); - return this._createPaymentTx(outputs, function(err, tx) { - if (err) return cb(err); + return this.getUnspent(function(err, unpsent) { + var ntxid = self.createPaymentTxSync(options, outputs, unspent); + if (ntxid) { + self.sendIndexes(); + self.sendTxProposal(ntxid); + self.store(); + self.emit('txProposalsUpdated'); + } self.log('You are currently on this BTC network:'); self.log(network); self.log('The server sent you a message:'); self.log(memo); - var refund_outputs = []; - - options.refund_to = options.refund_to || self.getAddresses()[0]; - - if (options.refund_to) { - var rpo = new PayPro(); - rpo = rpo.makeOutput(); - rpo.set('amount', 0); - rpo.set('script', - Buffer.concat( - new Buffer([ - 118, // OP_DUP - 169, // OP_HASH160 - 76, // OP_PUSHDATA1 - 20, // number of bytes - ]), - options.refund_to, - new Buffer([ - 136, // OP_EQUALVERIFY - 172 // OP_CHECKSIG - ]) - ) - ); - refund_outputs.push(rpo.message); - } - - // We send this to the serve after receiving a PaymentRequest - var pay = new PayPro(); - pay = pay.makePayment(); - pay.set('merchant_data', merchant_data); - pay.set('transactions', [tx.serialize()]); - pay.set('refund_to', refund_outputs); - - options.memo = options.memo || options.comment - || 'Hi server, I would like to give you some money.'; - - pay.set('memo', options.memo); - - options.payment_url = options.payment_url || payment_url; - - return self._sendPayment(tx, options, pay, cb); + return cb(ntxid); }); }; -Wallet.prototype._sendPayment = function(tx, options, pay, cb) { +Wallet.prototype.sendPaymentTx = function(ntxid, options, pay, cb) { var self = this; + + var txp = this.txProposals.txps[ntxid]; + if (!txp) return; + + var tx = txp.builder.build(); + if (!tx.isComplete()) return; + this.log('Sending Transaction'); + + var refund_outputs = []; + + options.refund_to = options.refund_to || self.getAddresses()[0]; + + if (options.refund_to) { + var total = 0; + for (var i = 0; i < tx.outs.length - 1; i++) { + total += tx.outs[i].v; + } + var rpo = new PayPro(); + rpo = rpo.makeOutput(); + rpo.set('amount', total); + rpo.set('script', + Buffer.concat([ + new Buffer([ + 118, // OP_DUP + 169, // OP_HASH160 + 76, // OP_PUSHDATA1 + 20, // number of bytes + ]), + options.refund_to, + new Buffer([ + 136, // OP_EQUALVERIFY + 172 // OP_CHECKSIG + ]) + ]) + ); + refund_outputs.push(rpo.message); + } + + // We send this to the serve after receiving a PaymentRequest + var pay = new PayPro(); + pay = pay.makePayment(); + pay.set('merchant_data', merchant_data); + pay.set('transactions', [tx.serialize()]); + pay.set('refund_to', refund_outputs); + + options.memo = options.memo || options.comment + || 'Hi server, I would like to give you some money.'; + + pay.set('memo', options.memo); + + options.payment_url = options.payment_url || payment_url; + return $http({ method: 'POST', url: options.payment_url, @@ -929,14 +935,14 @@ Wallet.prototype._sendPayment = function(tx, options, pay, cb) { data = PayPro.PaymentACK.decode(data); var ack = new PayPro(); ack = ack.makePaymentACK(data); - return self._receivePaymentRequestACK(tx, options, ack, cb); + return self.receivePaymentRequestACK(tx, options, ack, cb); }) .error(function(data, status, headers, config) { return cb(new Error('Status: ' + JSON.stringify(status))); }); }; -Wallet.prototype._receivePaymentRequestACK = function(tx, options, ack, cb) { +Wallet.prototype.receivePaymentRequestACK = function(tx, options, ack, cb) { var self = this; var payment = ack.get('payment'); var memo = ack.get('memo'); @@ -956,67 +962,85 @@ Wallet.prototype._receivePaymentRequestACK = function(tx, options, ack, cb) { return cb(txid, ca); }; -Wallet.prototype._createPaymentTx = function(outputs, cb) { +Wallet.prototype.createPaymentTxSync = function(options, outputs, unspent) { var self = this; - return this.getUnspent(function(err, unspent) { - if (err) return cb(err); - var outs = []; - outputs.forEach(function(output) { - outs.push({ - address: self.getAddressesStr()[0], // dummy address - amount: 0 // dummy value - }); + var outs = []; + outputs.forEach(function(output) { + outs.push({ + address: self.getAddressesStr()[0], // dummy address + amount: 0 // dummy value }); - - var opts = { - remainderOut: { - address: self._doGenerateAddress(true).toString() - } - }; - - var tx = new Builder(opts) - .setUnspent(unspent) - .setOutputs(outs); - - var priv = self.privateKey; - var pkr = self.publicKeyRing; - var selectedUtxos = tx.getSelectedUnspent(); - var inputChainPaths = selectedUtxos.map(function(utxo) { - return pkr.pathForAddress(utxo.address); - }); - if (priv) { - var keys = priv.getForPaths(inputChainPaths); - var signed = tx.sign(keys); - } - - tx = tx.sign(keys); - tx = tx.build(); - - outputs.forEach(function(output, i) { - var value = output.get('amount'); - var script = output.get('script'); - var v = new Buffer(8); - v[0] = (value.low >> 0) & 0xff; - v[1] = (value.low >> 8) & 0xff; - v[2] = (value.low >> 16) & 0xff; - v[3] = (value.low >> 24) & 0xff; - v[4] = (value.high >> 0) & 0xff; - v[5] = (value.high >> 8) & 0xff; - v[6] = (value.high >> 16) & 0xff; - v[7] = (value.high >> 24) & 0xff; - var s = script.buffer.slice(script.offset, script.limit); - tx.outs[i].v = v; - tx.outs[i].s = s; - }); - - self.log(''); - self.log('Created transaction:'); - self.log(tx.getStandardizedObject()); - self.log(''); - - return cb(null, tx); }); + + var opts = { + remainderOut: { + address: this._doGenerateAddress(true).toString() + } + }; + + var b = new Builder(opts) + .setUnspent(unspent) + .setOutputs(outs); + + var priv = this.privateKey; + var pkr = this.publicKeyRing; + var selectedUtxos = b.getSelectedUnspent(); + var inputChainPaths = selectedUtxos.map(function(utxo) { + return pkr.pathForAddress(utxo.address); + }); + if (priv) { + var keys = priv.getForPaths(inputChainPaths); + var signed = b.sign(keys); + } + + b = b.sign(keys); + + outputs.forEach(function(output, i) { + var value = output.get('amount'); + var script = output.get('script'); + var v = new Buffer(8); + v[0] = (value.low >> 0) & 0xff; + v[1] = (value.low >> 8) & 0xff; + v[2] = (value.low >> 16) & 0xff; + v[3] = (value.low >> 24) & 0xff; + v[4] = (value.high >> 0) & 0xff; + v[5] = (value.high >> 8) & 0xff; + v[6] = (value.high >> 16) & 0xff; + v[7] = (value.high >> 24) & 0xff; + var s = script.buffer.slice(script.offset, script.limit); + b.tx.outs[i].v = v; + b.tx.outs[i].s = s; + }); + + this.log(''); + this.log('Created transaction:'); + this.log(tx.getStandardizedObject()); + this.log(''); + + var myId = this.getMyCopayerId(); + var now = Date.now(); + + var me = {}; + + var tx = b.build(); + if (priv && tx.countInputSignatures(0)) me[myId] = now; + + var meSeen = {}; + if (priv) meSeen[myId] = now; + + var data = { + inputChainPaths: inputChainPaths, + signedBy: me, + seenBy: meSeen, + creator: myId, + createdTs: now, + builder: b, + comment: options.memo + }; + + var ntxid = this.txProposals.add(data); + return ntxid; }; Wallet.prototype.addSeenToTxProposals = function() { From d7b1770e5a0a02b5c0f81499d89c527c233b37ab Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Mon, 28 Jul 2014 00:06:03 -0700 Subject: [PATCH 004/178] paypro: add merchant prop to tx proposal. --- js/models/core/Wallet.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index efb01d057..24289b2ff 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -1036,7 +1036,8 @@ Wallet.prototype.createPaymentTxSync = function(options, outputs, unspent) { creator: myId, createdTs: now, builder: b, - comment: options.memo + comment: options.memo, + merchant: options.uri }; var ntxid = this.txProposals.add(data); From 59038a5d306fda5e958057d7be50cf48a845ae6b Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Mon, 28 Jul 2014 09:48:45 -0700 Subject: [PATCH 005/178] paypro: fix how merchant data is stored. --- js/models/core/Wallet.js | 64 ++++++++++++++++++++++++++-------------- 1 file changed, 42 insertions(+), 22 deletions(-) diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index 24289b2ff..e2dc9b51d 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -733,10 +733,10 @@ Wallet.prototype.sign = function(ntxid, cb) { Wallet.prototype.sendTx = function(ntxid, cb) { var txp = this.txProposals.get(ntxid); - // if (txp.merchant) { - // return this.createPaymentTx(ntxid, - // { uri: txp.merchant, txp: txp }, cb); - // } + if (txp.merchant) { + return this.sendPaymentTx(ntxid, + { uri: txp.merchant.uri, txp: txp }, cb); + } var tx = txp.builder.build(); if (!tx.isComplete()) @@ -768,11 +768,6 @@ Wallet.prototype.sendTx = function(ntxid, cb) { }); }; -// NOTE: -// This process is currently backwards. This is really awkward to handle -// because the tx outputs are given to us by the server in the payment details. -// This is just preliminary code, not meant to be used. - Wallet.prototype.createPaymentTx = function(ntxid, options, cb) { var self = this; @@ -836,6 +831,7 @@ Wallet.prototype.receivePaymentRequest = function(tx, options, pr, cb) { 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'); @@ -844,8 +840,29 @@ Wallet.prototype.receivePaymentRequest = function(tx, options, pr, cb) { var payment_url = pd.get('payment_url'); var merchant_data = pd.get('merchant_data'); + var merchantData = { + pr: { + ver: ver, + pki_type: pki_type, + pki_data: pki_data, + details: details, + pd: { + network: network, + outputs: outputs, + time: time, + expires: expires, + memo: memo, + payment_url: payment_url, + merchant_data: merchant_data + }, + sig: signature, + certs: certs, + ca: ca + } + }; + return this.getUnspent(function(err, unpsent) { - var ntxid = self.createPaymentTxSync(options, outputs, unspent); + var ntxid = self.createPaymentTxSync(options, merchantData, unspent); if (ntxid) { self.sendIndexes(); self.sendTxProposal(ntxid); @@ -862,7 +879,7 @@ Wallet.prototype.receivePaymentRequest = function(tx, options, pr, cb) { }); }; -Wallet.prototype.sendPaymentTx = function(ntxid, options, pay, cb) { +Wallet.prototype.sendPaymentTx = function(ntxid, options, cb) { var self = this; var txp = this.txProposals.txps[ntxid]; @@ -905,16 +922,16 @@ Wallet.prototype.sendPaymentTx = function(ntxid, options, pay, cb) { // We send this to the serve after receiving a PaymentRequest var pay = new PayPro(); pay = pay.makePayment(); - pay.set('merchant_data', merchant_data); + pay.set('merchant_data', txp.merchant.pd.merchant_data); pay.set('transactions', [tx.serialize()]); pay.set('refund_to', refund_outputs); - options.memo = options.memo || options.comment + txp.merchant.memo = txp.merchant.memo || txp.merchant.comment || 'Hi server, I would like to give you some money.'; - pay.set('memo', options.memo); + pay.set('memo', txp.merchant.memo); - options.payment_url = options.payment_url || payment_url; + options.payment_url = options.payment_url || txp.merchant.payment_url; return $http({ method: 'POST', @@ -959,14 +976,14 @@ Wallet.prototype.receivePaymentRequestACK = function(tx, options, ack, cb) { tx = ptx; } var txid = tx.getHash().toString('hex'); - return cb(txid, ca); + return cb(txid, options.txp.pr.ca); }; -Wallet.prototype.createPaymentTxSync = function(options, outputs, unspent) { +Wallet.prototype.createPaymentTxSync = function(options, merchantData, unspent) { var self = this; var outs = []; - outputs.forEach(function(output) { + merchantData.pr.pd.outputs.forEach(function(output) { outs.push({ address: self.getAddressesStr()[0], // dummy address amount: 0 // dummy value @@ -994,9 +1011,7 @@ Wallet.prototype.createPaymentTxSync = function(options, outputs, unspent) { var signed = b.sign(keys); } - b = b.sign(keys); - - outputs.forEach(function(output, i) { + merchantData.pr.pd.outputs.forEach(function(output, i) { var value = output.get('amount'); var script = output.get('script'); var v = new Buffer(8); @@ -1037,7 +1052,7 @@ Wallet.prototype.createPaymentTxSync = function(options, outputs, unspent) { createdTs: now, builder: b, comment: options.memo, - merchant: options.uri + merchant: merchantData }; var ntxid = this.txProposals.add(data); @@ -1157,6 +1172,11 @@ Wallet.prototype.createTx = function(toAddress, amountSatStr, comment, opts, cb) } opts = opts || {}; + if (opts.merchant) { + return this.createPaymentTx(ntxid, + { uri: opts.merchant }, cb); + } + if (typeof opts.spendUnconfirmed === 'undefined') { opts.spendUnconfirmed = this.spendUnconfirmed; } From 61ef19ad1988abee924ba7b3c4173414bd2171ff Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Mon, 28 Jul 2014 10:44:45 -0700 Subject: [PATCH 006/178] paypro: fix a lot of aspects of storing merchant data in txps. --- js/models/core/Wallet.js | 95 ++++++++++++++++++++++++---------------- 1 file changed, 57 insertions(+), 38 deletions(-) diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index e2dc9b51d..560f33670 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -734,8 +734,7 @@ Wallet.prototype.sendTx = function(ntxid, cb) { var txp = this.txProposals.get(ntxid); if (txp.merchant) { - return this.sendPaymentTx(ntxid, - { uri: txp.merchant.uri, txp: txp }, cb); + return this.sendPaymentTx(ntxid, cb); } var tx = txp.builder.build(); @@ -768,7 +767,7 @@ Wallet.prototype.sendTx = function(ntxid, cb) { }); }; -Wallet.prototype.createPaymentTx = function(ntxid, options, cb) { +Wallet.prototype.createPaymentTx = function(options, cb) { var self = this; if (typeof options === 'string') { @@ -809,13 +808,13 @@ Wallet.prototype.receivePaymentRequest = function(tx, options, pr, cb) { var certs = PayPro.X509Certificates.decode(pki_data); certs = certs.certificate; - var trusted = certs.every(function(cert) { + var trusted = certs.map(function(cert) { var der = cert.toString('hex'); var pem = PayPro.prototype._DERtoPEM(der, 'CERTIFICATE'); return RootCerts.getTrusted(pem); }); - if (!trusted) { + if (!trusted.length) { return cb(new Error('Not a trusted certificate.')); } @@ -826,7 +825,7 @@ Wallet.prototype.receivePaymentRequest = function(tx, options, pr, cb) { return cb(new Error('Server sent a bad signature.')); } - options.ca = trusted[0]; + var ca = trusted[0]; details = PayPro.PaymentDetails.decode(details); var pd = new PayPro(); @@ -842,21 +841,28 @@ Wallet.prototype.receivePaymentRequest = function(tx, options, pr, cb) { var merchantData = { pr: { - ver: ver, + payment_details_version: ver, pki_type: pki_type, - pki_data: pki_data, - details: details, + // pki_data: pki_data.toString('hex'), + pki_data: certs, + // serialized_payment_details: details.serialize().toString('hex'), pd: { network: network, - outputs: outputs, + // outputs: outputs, + outputs: outputs.map(function(output) { + return { + amount: output.get('amount'), + script: output.get('script').toString('hex') + }; + }), time: time, expires: expires, memo: memo, payment_url: payment_url, - merchant_data: merchant_data + merchant_data: merchant_data.toString('hex') }, - sig: signature, - certs: certs, + signature: sig, + // certs: certs, ca: ca } }; @@ -882,6 +888,11 @@ Wallet.prototype.receivePaymentRequest = function(tx, options, pr, cb) { Wallet.prototype.sendPaymentTx = function(ntxid, options, cb) { var self = this; + if (!cb) { + cb = options; + options = {}; + } + var txp = this.txProposals.txps[ntxid]; if (!txp) return; @@ -922,20 +933,26 @@ Wallet.prototype.sendPaymentTx = function(ntxid, options, cb) { // We send this to the serve after receiving a PaymentRequest var pay = new PayPro(); pay = pay.makePayment(); - pay.set('merchant_data', txp.merchant.pd.merchant_data); + var merchant_data = txp.merchant.pd.merchant_data; + if (typeof merchant_data === 'string') { + merchant_data = new Buffer(merchant_data, 'hex'); + } + pay.set('merchant_data', merchant_data); pay.set('transactions', [tx.serialize()]); pay.set('refund_to', refund_outputs); - txp.merchant.memo = txp.merchant.memo || txp.merchant.comment + // XXX This is actually the server memo - change! + // txp.merchant.pr.pd.memo = txp.merchant.pr.pd.memo + // || 'Hi server, I would like to give you some money.'; + + options.memo = options.memo || options.comment || 'Hi server, I would like to give you some money.'; - pay.set('memo', txp.merchant.memo); - - options.payment_url = options.payment_url || txp.merchant.payment_url; + pay.set('memo', txp.merchant.pr.pd.memo); return $http({ method: 'POST', - url: options.payment_url, + url: txp.merchant.pr.pd.payment_url, headers: { // BIP-71 'Accept': PayPro.PAYMENT_REQUEST_CONTENT_TYPE @@ -952,14 +969,14 @@ Wallet.prototype.sendPaymentTx = function(ntxid, options, cb) { data = PayPro.PaymentACK.decode(data); var ack = new PayPro(); ack = ack.makePaymentACK(data); - return self.receivePaymentRequestACK(tx, options, ack, cb); + return self.receivePaymentRequestACK(tx, txp, ack, cb); }) .error(function(data, status, headers, config) { return cb(new Error('Status: ' + JSON.stringify(status))); }); }; -Wallet.prototype.receivePaymentRequestACK = function(tx, options, ack, cb) { +Wallet.prototype.receivePaymentRequestACK = function(tx, txp, ack, cb) { var self = this; var payment = ack.get('payment'); var memo = ack.get('memo'); @@ -976,7 +993,7 @@ Wallet.prototype.receivePaymentRequestACK = function(tx, options, ack, cb) { tx = ptx; } var txid = tx.getHash().toString('hex'); - return cb(txid, options.txp.pr.ca); + return cb(txid, txp.pr.ca); }; Wallet.prototype.createPaymentTxSync = function(options, merchantData, unspent) { @@ -986,7 +1003,7 @@ Wallet.prototype.createPaymentTxSync = function(options, merchantData, unspent) merchantData.pr.pd.outputs.forEach(function(output) { outs.push({ address: self.getAddressesStr()[0], // dummy address - amount: 0 // dummy value + amount: 0 // dummy amount }); }); @@ -1012,17 +1029,17 @@ Wallet.prototype.createPaymentTxSync = function(options, merchantData, unspent) } merchantData.pr.pd.outputs.forEach(function(output, i) { - var value = output.get('amount'); - var script = output.get('script'); + var amount = output.get ? output.get('amount') : output.amount; + var script = output.get ? output.get('script') : new Buffer(output.script, 'hex'); var v = new Buffer(8); - v[0] = (value.low >> 0) & 0xff; - v[1] = (value.low >> 8) & 0xff; - v[2] = (value.low >> 16) & 0xff; - v[3] = (value.low >> 24) & 0xff; - v[4] = (value.high >> 0) & 0xff; - v[5] = (value.high >> 8) & 0xff; - v[6] = (value.high >> 16) & 0xff; - v[7] = (value.high >> 24) & 0xff; + v[0] = (amount.low >> 0) & 0xff; + v[1] = (amount.low >> 8) & 0xff; + v[2] = (amount.low >> 16) & 0xff; + v[3] = (amount.low >> 24) & 0xff; + v[4] = (amount.high >> 0) & 0xff; + v[5] = (amount.high >> 8) & 0xff; + v[6] = (amount.high >> 16) & 0xff; + v[7] = (amount.high >> 24) & 0xff; var s = script.buffer.slice(script.offset, script.limit); b.tx.outs[i].v = v; b.tx.outs[i].s = s; @@ -1166,17 +1183,19 @@ Wallet.prototype.getUnspent = function(cb) { Wallet.prototype.createTx = function(toAddress, amountSatStr, comment, opts, cb) { var self = this; + + if (typeof comment === 'undefined') { + var cb = amountSatStr; + var merchant = toAddress; + return this.createPaymentTx({ uri: merchant }, cb); + } + if (typeof opts === 'function') { cb = opts; opts = {}; } opts = opts || {}; - if (opts.merchant) { - return this.createPaymentTx(ntxid, - { uri: opts.merchant }, cb); - } - if (typeof opts.spendUnconfirmed === 'undefined') { opts.spendUnconfirmed = this.spendUnconfirmed; } From d7ec908701216e27273bc8f45cbc7e93d15910f0 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Mon, 28 Jul 2014 10:46:36 -0700 Subject: [PATCH 007/178] paypro: remove old code. --- js/models/core/Wallet.js | 8 -------- 1 file changed, 8 deletions(-) diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index 560f33670..0002baaca 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -843,12 +843,9 @@ Wallet.prototype.receivePaymentRequest = function(tx, options, pr, cb) { pr: { payment_details_version: ver, pki_type: pki_type, - // pki_data: pki_data.toString('hex'), pki_data: certs, - // serialized_payment_details: details.serialize().toString('hex'), pd: { network: network, - // outputs: outputs, outputs: outputs.map(function(output) { return { amount: output.get('amount'), @@ -862,7 +859,6 @@ Wallet.prototype.receivePaymentRequest = function(tx, options, pr, cb) { merchant_data: merchant_data.toString('hex') }, signature: sig, - // certs: certs, ca: ca } }; @@ -941,10 +937,6 @@ Wallet.prototype.sendPaymentTx = function(ntxid, options, cb) { pay.set('transactions', [tx.serialize()]); pay.set('refund_to', refund_outputs); - // XXX This is actually the server memo - change! - // txp.merchant.pr.pd.memo = txp.merchant.pr.pd.memo - // || 'Hi server, I would like to give you some money.'; - options.memo = options.memo || options.comment || 'Hi server, I would like to give you some money.'; From b0dc3fc24d52273de25e6c4e8c7faefe8041466f Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Mon, 28 Jul 2014 14:46:08 -0700 Subject: [PATCH 008/178] paypro: more merchant data storage. createTx and sendTx work. --- js/controllers/send.js | 14 ++++++++++++-- js/models/core/Wallet.js | 34 ++++++++++++++++++++++++++++------ 2 files changed, 40 insertions(+), 8 deletions(-) diff --git a/js/controllers/send.js b/js/controllers/send.js index 3b1568382..05d6e3596 100644 --- a/js/controllers/send.js +++ b/js/controllers/send.js @@ -61,10 +61,14 @@ angular.module('copayApp.controllers').controller('SendController', var w = $rootScope.wallet; - w.createTx(address, amount, commentText, function(ntxid) { + function done(ntxid, ca) { if (w.isShared()) { $scope.loading = false; var message = 'The transaction proposal has been created'; + if (ca) { + message += '.\nThis payment protocol transaction + + 'has been verified through ' + ca; + } notification.success('Success!', message); $scope.loadTxs(); } else { @@ -80,7 +84,13 @@ angular.module('copayApp.controllers').controller('SendController', }); } $rootScope.pendingPayment = null; - }); + } + + if (~address.indexOf('://')) { + w.createTx(address, commentText, done); + } else { + w.createTx(address, amount, commentText, done); + } // reset fields $scope.address = $scope.amount = $scope.commentText = null; diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index 0002baaca..e1e1773e7 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -815,7 +815,10 @@ Wallet.prototype.receivePaymentRequest = function(tx, options, pr, cb) { }); if (!trusted.length) { - return cb(new Error('Not a trusted certificate.')); + var G = typeof window !== 'undefined' ? window : global; + if (!G.SSL_UNTRUSTED) { + return cb(new Error('Not a trusted certificate.')); + } } // Verify Signature @@ -849,7 +852,11 @@ Wallet.prototype.receivePaymentRequest = function(tx, options, pr, cb) { outputs: outputs.map(function(output) { return { amount: output.get('amount'), - script: output.get('script').toString('hex') + script: { + offset: output.get('script').offset, + limit: output.get('script').limit, + buffer: output.get('script').buffer.toString('hex') + } }; }), time: time, @@ -877,7 +884,7 @@ Wallet.prototype.receivePaymentRequest = function(tx, options, pr, cb) { self.log('The server sent you a message:'); self.log(memo); - return cb(ntxid); + return cb(ntxid, ca); }); }; @@ -1021,8 +1028,14 @@ Wallet.prototype.createPaymentTxSync = function(options, merchantData, unspent) } merchantData.pr.pd.outputs.forEach(function(output, i) { - var amount = output.get ? output.get('amount') : output.amount; - var script = output.get ? output.get('script') : new Buffer(output.script, 'hex'); + var amount = output.get + ? output.get('amount') + : output.amount; + + var script = output.get + ? output.get('script') + : new Buffer(output.script, 'hex'); + var v = new Buffer(8); v[0] = (amount.low >> 0) & 0xff; v[1] = (amount.low >> 8) & 0xff; @@ -1032,7 +1045,9 @@ Wallet.prototype.createPaymentTxSync = function(options, merchantData, unspent) v[5] = (amount.high >> 8) & 0xff; v[6] = (amount.high >> 16) & 0xff; v[7] = (amount.high >> 24) & 0xff; + var s = script.buffer.slice(script.offset, script.limit); + b.tx.outs[i].v = v; b.tx.outs[i].s = s; }); @@ -1176,12 +1191,19 @@ Wallet.prototype.getUnspent = function(cb) { Wallet.prototype.createTx = function(toAddress, amountSatStr, comment, opts, cb) { var self = this; - if (typeof comment === 'undefined') { + if (typeof amountSatStr === 'function') { var cb = amountSatStr; var merchant = toAddress; return this.createPaymentTx({ uri: merchant }, cb); } + if (typeof comment === 'function') { + var cb = comment; + var merchant = toAddress; + var comment = amountSatStr; + return this.createPaymentTx({ uri: merchant, memo: comment }, cb); + } + if (typeof opts === 'function') { cb = opts; opts = {}; From b18c9e3cd5fdcd7ac0be1cf680889895f3601e80 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Mon, 28 Jul 2014 18:34:15 -0700 Subject: [PATCH 009/178] paypro: fix a lot of errors in payment protocol implementation. --- js/models/core/Wallet.js | 53 ++++++++++++++++++++++++++-------------- 1 file changed, 35 insertions(+), 18 deletions(-) diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index e1e1773e7..4fa3203c0 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -789,14 +789,14 @@ Wallet.prototype.createPaymentTx = function(options, cb) { data = PayPro.PaymentRequest.decode(data); var pr = new PayPro(); pr = pr.makePaymentRequest(data); - return self.receivePaymentRequest(tx, options, pr, cb); + return self.receivePaymentRequest(options, pr, cb); }) .error(function(data, status, headers, config) { return cb(new Error('Status: ' + JSON.stringify(status))); }); }; -Wallet.prototype.receivePaymentRequest = function(tx, options, pr, cb) { +Wallet.prototype.receivePaymentRequest = function(options, pr, cb) { var self = this; var ver = pr.get('payment_details_version'); @@ -808,15 +808,21 @@ Wallet.prototype.receivePaymentRequest = function(tx, options, pr, cb) { var certs = PayPro.X509Certificates.decode(pki_data); certs = certs.certificate; + // XXX Temporary fix for tests + if (!PayPro.RootCerts) { + PayPro.RootCerts = { + getTrusted: function() {} + }; + } + var trusted = certs.map(function(cert) { var der = cert.toString('hex'); var pem = PayPro.prototype._DERtoPEM(der, 'CERTIFICATE'); - return RootCerts.getTrusted(pem); + return PayPro.RootCerts.getTrusted(pem); }); if (!trusted.length) { - var G = typeof window !== 'undefined' ? window : global; - if (!G.SSL_UNTRUSTED) { + if (typeof SSL_UNTRUSTED === 'undefined') { return cb(new Error('Not a trusted certificate.')); } } @@ -870,7 +876,7 @@ Wallet.prototype.receivePaymentRequest = function(tx, options, pr, cb) { } }; - return this.getUnspent(function(err, unpsent) { + return this.getUnspent(function(err, unspent) { var ntxid = self.createPaymentTxSync(options, merchantData, unspent); if (ntxid) { self.sendIndexes(); @@ -905,7 +911,7 @@ Wallet.prototype.sendPaymentTx = function(ntxid, options, cb) { var refund_outputs = []; - options.refund_to = options.refund_to || self.getAddresses()[0]; + options.refund_to = options.refund_to || self.getPubKeys()[0]; if (options.refund_to) { var total = 0; @@ -997,14 +1003,12 @@ Wallet.prototype.receivePaymentRequestACK = function(tx, txp, ack, cb) { Wallet.prototype.createPaymentTxSync = function(options, merchantData, unspent) { var self = this; + var priv = this.privateKey; + var pkr = this.publicKeyRing; - var outs = []; - merchantData.pr.pd.outputs.forEach(function(output) { - outs.push({ - address: self.getAddressesStr()[0], // dummy address - amount: 0 // dummy amount - }); - }); + // preconditions.checkArgument(new Address(toAddress).network().name === this.getNetworkName()); + preconditions.checkState(pkr.isComplete()); + if (options.memo) preconditions.checkArgument(options.memo.length <= 100); var opts = { remainderOut: { @@ -1012,16 +1016,25 @@ Wallet.prototype.createPaymentTxSync = function(options, merchantData, unspent) } }; + var outs = []; + merchantData.pr.pd.outputs.forEach(function(output) { + outs.push({ + address: self.getAddressesStr()[0] || '1NGYre1pSqTnCXaqN5gLQ1e2KNTJXjDhtF', // dummy address + amountSatStr: '0' // dummy amount + }); + }); + var b = new Builder(opts) .setUnspent(unspent) .setOutputs(outs); - var priv = this.privateKey; - var pkr = this.publicKeyRing; 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); @@ -1034,7 +1047,11 @@ Wallet.prototype.createPaymentTxSync = function(options, merchantData, unspent) var script = output.get ? output.get('script') - : new Buffer(output.script, 'hex'); + : { + offset: output.script.offset, + limit: output.script.limit, + buffer: new Buffer(output.script.buffer, 'hex') + }; var v = new Buffer(8); v[0] = (amount.low >> 0) & 0xff; @@ -1054,7 +1071,7 @@ Wallet.prototype.createPaymentTxSync = function(options, merchantData, unspent) this.log(''); this.log('Created transaction:'); - this.log(tx.getStandardizedObject()); + this.log(b.tx.getStandardizedObject()); this.log(''); var myId = this.getMyCopayerId(); From 58b9345f59d41b5d1d987116b9e3cbf6cbadd8dc Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Mon, 28 Jul 2014 18:36:20 -0700 Subject: [PATCH 010/178] test: fix wallet test if copay is in ./node_modules. --- test/test.Wallet.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/test.Wallet.js b/test/test.Wallet.js index 4139da669..f39287428 100644 --- a/test/test.Wallet.js +++ b/test/test.Wallet.js @@ -3,9 +3,10 @@ var chai = chai || require('chai'); var should = chai.should(); var sinon = require('sinon'); -try { +var is_browser = (typeof process == 'undefined' || typeof process.versions === 'undefined'); +if (is_browser) { var copay = require('copay'); //browser -} catch (e) { +} else { var copay = require('../copay'); //node } var copayConfig = require('../config'); From e06fda5128d0f0670e9350434eb746a7c3ae13f8 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Mon, 28 Jul 2014 18:45:18 -0700 Subject: [PATCH 011/178] paypro: fix more payment protocol errors. --- js/models/core/Wallet.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index 4fa3203c0..f5b33ca77 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -911,7 +911,8 @@ Wallet.prototype.sendPaymentTx = function(ntxid, options, cb) { var refund_outputs = []; - options.refund_to = options.refund_to || self.getPubKeys()[0]; + options.refund_to = options.refund_to + || self.publicKeyRing.getPubKeys(0, false, this.getMyCopayerId())[0]; if (options.refund_to) { var total = 0; @@ -942,7 +943,7 @@ Wallet.prototype.sendPaymentTx = function(ntxid, options, cb) { // We send this to the serve after receiving a PaymentRequest var pay = new PayPro(); pay = pay.makePayment(); - var merchant_data = txp.merchant.pd.merchant_data; + var merchant_data = txp.merchant.pr.pd.merchant_data; if (typeof merchant_data === 'string') { merchant_data = new Buffer(merchant_data, 'hex'); } From f6e9d647b45e196bc391ff869b0ca4a3ab645744 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Mon, 28 Jul 2014 18:45:51 -0700 Subject: [PATCH 012/178] paypro: add hideous test for payment protocol. --- test/test.PayPro.js | 459 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 459 insertions(+) create mode 100644 test/test.PayPro.js diff --git a/test/test.PayPro.js b/test/test.PayPro.js new file mode 100644 index 000000000..709a4a505 --- /dev/null +++ b/test/test.PayPro.js @@ -0,0 +1,459 @@ +'use strict'; + +var chai = chai || require('chai'); +var should = chai.should(); +var sinon = require('sinon'); +var is_browser = (typeof process == 'undefined' || typeof process.versions === 'undefined'); +if (is_browser) { + var copay = require('copay'); //browser +} else { + var copay = require('../copay'); //node +} +var copayConfig = require('../config'); +var Wallet = require('../js/models/core/Wallet'); +var Structure = copay.Structure; +var Storage = require('./mocks/FakeStorage'); +var Network = require('./mocks/FakeNetwork'); +var Blockchain = require('./mocks/FakeBlockchain'); +var bitcore = bitcore || require('bitcore'); +var TransactionBuilder = bitcore.TransactionBuilder; +var Transaction = bitcore.Transaction; +var Address = bitcore.Address; + +var addCopayers = function(w) { + for (var i = 0; i < 4; i++) { + w.publicKeyRing.addCopayer(); + } +}; + + + +var chai = chai || require('chai'); +var should = chai.should(); + +var FakeStorage = require('./mocks/FakeLocalStorage'); +//var copay = copay || require('../copay'); +var sinon = require('sinon'); +var FakeNetwork = require('./mocks/FakeNetwork'); +var FakeBlockchain = require('./mocks/FakeBlockchain'); +var FakeStorage = require('./mocks/FakeStorage'); +var WalletFactory = require('../js/models/core/WalletFactory'); +var Passphrase = require('../js/models/core/Passphrase'); + + + +var G = typeof window !== 'undefined' ? window : global; +G.SSL_UNTRUSTED = true; + +if (!is_browser) { + // Disable strictSSL + process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = '0'; + + var _request = require('request'); + G.$http = function(options) { + var ret = { + success: function(cb) { + this._success = cb; + return this; + }, + error: function(cb) { + this._error = cb; + return this; + } + }; + if (options.responseType === 'arraybuffer') { + delete options.responseType; + options.encoding = null; + } + _request(options, function(err, res, body) { + if (err) return ret._error(null, null, null, options); + return ret._success(body, res.statusCode, res.headers, options); + }); + return ret; + }; +} + +function startServer_() { + if (is_browser) { + return cb(null, { + close: function() { + ; + } + }); + } + + var spawn = require('child_process').spawn; + var path = require('path'); + + //var bc = path.dirname(require.resolve('bitcore/package.json')); + //var bc = path.dirname(require.resolve(__dirname + '/../node_modules/bitcore/package.json')); + var bc = path.dirname(require.resolve(__dirname + '/../../bitcore/package.json')); + + var options = { + cwd: process.cwd(), + env: process.env, + setsid: false, + customFds: [-1, -1, -1] + }; + + var ps = spawn('node', + [bc + '/examples/PayPro/index.js', '-p', '8080'], + options); + + ps.close = function() { + ps.once('error', function() { + server.kill('SIGKILL'); + }); + ps.kill('SIGTERM'); + }; + + process.on('exit', function() { + ps.close(); + }); + + return cb(null, ps); +} + +function startServer(cb) { + if (is_browser) { + return cb(null, { + close: function() { + ; + } + }); + } + + var path = require('path'); + var bc = path.dirname(require.resolve(__dirname + '/../../bitcore/package.json')); + var example = bc + '/examples/PayPro/server.js'; + var server = require(example); + + server.listen(8080, function(addr) { + return cb(null, server); + }); +} + +// var server = startServer(); +// server.uri = 'https://localhost:8080/-'; + +var server; + +/* +describe('PayPro (in Wallet) model', function() { + var config = { + Network: FakeNetwork, + Blockchain: FakeBlockchain, + Storage: FakeStorage, + wallet: { + // requiredCopayers: 3, + // totalCopayers: 5, + requiredCopayers: 1, + totalCopayers: 1, + spendUnconfirmed: 1, + reconnectDelay: 100, + }, + blockchain: { + host: 'test.insight.is', + port: 80 + }, + networkName: 'testnet', + passphrase: 'test', + storageObj: new FakeStorage(), + networkObj: new FakeNetwork(), + blockchainObj: new FakeBlockchain(), + }; + + beforeEach(function() { + config.storageObj.reset(); + }); + + it('should start the example server', function(done) { + startServer(function(err, s) { + if (err) return done(err); + server = s; + server.uri = 'https://localhost:8080/-'; + done(); + }); + }); + + it('should be able to create wallets', function(done) { + var wf = new WalletFactory(config, '0.0.1'); + var w = wf.create(); + + var unspentTest = [{ + 'address': null, + "scriptPubKey": null, + "txid": "2ac165fa7a3a2b535d106a0041c7568d03b531e58aeccdd3199d7289ab12cfc1", + "vout": 1, + "amount": 10, + "confirmations": 7 + + , hashToScriptMap: { + '2N6fdPg2QL7V36XKe7a8wkkA5HCy7fNYmZF': '5321027445ab3a935dce7aee1dadb0d103ed6147a0f83deb80474a04538b2c5bc4d5092102ab32ba51402a139873aeb919c738f5a945f3956f8f8c6ba296677bd29e85d7e821036f119b72e09f76c11ebe2cf754d64eac2cb42c9e623455d54aaa89d70c11f9c82103bcbd3f8ab2c849ea9eae434733cee8b75120d26233def56011b3682ca12081d72103f37f81dc534163b9f73ecf36b91e6c3fb8ae370c24618f91bb1d972e86ceeee255ae' + } + }]; + + unspentTest[0].address = w.publicKeyRing.getAddress(1, true, w.publicKey).toString(); + unspentTest[0].scriptPubKey = w.publicKeyRing.getScriptPubKeyHex(1, true, w.publicKey); + + if (0) + var unspentTest = [{ + address: '2N6fdPg2QL7V36XKe7a8wkkA5HCy7fNYmZF', + scriptPubKey: 'a91493372782bab70f4eefdefefea8ece0df44f9596887', + txid: '2ac165fa7a3a2b535d106a0041c7568d03b531e58aeccdd3199d7289ab12cfc1', + vout: 1, + amount: 10, + confirmations: 7, + scriptSig: ['00493046022100b8249a4fc326c4c33882e9d5468a1c6faa01e8c6cef0a24970122e804abdd860022100dbf6ee3b07d3aad8f73997e62ad20654a08aa63a7609792d02f3d5d088e69ad9014cad5321027445ab3a935dce7aee1dadb0d103ed6147a0f83deb80474a04538b2c5bc4d5092102ab32ba51402a139873aeb919c738f5a945f3956f8f8c6ba296677bd29e85d7e821036f119b72e09f76c11ebe2cf754d64eac2cb42c9e623455d54aaa89d70c11f9c82103bcbd3f8ab2c849ea9eae434733cee8b75120d26233def56011b3682ca12081d72103f37f81dc534163b9f73ecf36b91e6c3fb8ae370c24618f91bb1d972e86ceeee255ae'], + hashToScriptMap: { + '2N6fdPg2QL7V36XKe7a8wkkA5HCy7fNYmZF': '5321027445ab3a935dce7aee1dadb0d103ed6147a0f83deb80474a04538b2c5bc4d5092102ab32ba51402a139873aeb919c738f5a945f3956f8f8c6ba296677bd29e85d7e821036f119b72e09f76c11ebe2cf754d64eac2cb42c9e623455d54aaa89d70c11f9c82103bcbd3f8ab2c849ea9eae434733cee8b75120d26233def56011b3682ca12081d72103f37f81dc534163b9f73ecf36b91e6c3fb8ae370c24618f91bb1d972e86ceeee255ae' + } + }]; + + // Addresses + var addrs = [ + 'mzTQ66VKcybz9BD1LAqEwMFp9NrBGS82sY', + 'mmu9k3KzsDMEm9JxmJmZaLhovAoRKW3zr4', + 'myqss64GNZuWuFyg5LTaoTCyWEpKH56Fgz' + ]; + + // Private keys in WIF format (see TransactionBuilder.js for other options) + var keys = [ + 'cVvr5YmWVAkVeZWAawd2djwXM4QvNuwMdCw1vFQZBM1SPFrtE8W8', + 'cPyx1hXbe3cGQcHZbW3GNSshCYZCriidQ7afR2EBsV6ReiYhSkNF' + // 'cUB9quDzq1Bj7pocenmofzNQnb1wJNZ5V3cua6pWKzNL1eQtaDqQ' + ]; + + var unspent = [{ + // http://blockexplorer.com/testnet/rawtx/1fcfe898cc2612f8b222bd3b4ac8d68bf95d43df8367b71978c184dea35bde22 + 'txid': '1fcfe898cc2612f8b222bd3b4ac8d68bf95d43df8367b71978c184dea35bde22', + 'vout': 1, + 'address': addrs[0], + 'scriptPubKey': '76a94c14cfbe41f4a518edc25af71bafc72fb61bfcfc4fcd88ac', + 'amount': 1.60000000, + 'confirmations': 9 + }, + + { + // http://blockexplorer.com/testnet/rawtx/0624c0c794447b0d2343ae3d20382983f41b915bb115a834419e679b2b13b804 + 'txid': '0624c0c794447b0d2343ae3d20382983f41b915bb115a834419e679b2b13b804', + 'vout': 1, + 'address': addrs[1], + 'scriptPubKey': '76a94c14460376539c219c5e3274d86f16b40e806b37817688ac', + 'amount': 1.60000000, + 'confirmations': 9 + } + ]; + + + should.exist(w); + + w.getUnspent = function(cb) { + return setTimeout(function() { + return cb(null, unspentTest, []); + }, 1); + }; + + var address = server.uri + '/request'; + var commentText = 'Hello, server. I\'d like to make a payment.'; + w.createTx(address, commentText, function(ntxid, ca) { + if (w.totalCopayers > 1) { + should.exist(ntxid); + console.log('Sent TX proposal to other copayers:'); + console.log([ntxid, ca]); + server.close(); + done(); + } else { + console.log('Sending TX to merchant server:'); + console.log(ntxid); + w.sendTx(ntxid, function(txid, ca) { + should.exist(txid); + console.log('TX sent:'); + console.log([ntxid, ca]); + server.close(); + done(); + }); + } + }); + }); +}); +*/ + +describe('PayPro (in Wallet) model', function() { + var config = { + // requiredCopayers: 3, + // totalCopayers: 5, + requiredCopayers: 1, + totalCopayers: 1, + spendUnconfirmed: true, + reconnectDelay: 100, + networkName: 'testnet', + }; + + var createW = function(netKey, N, conf) { + var c = JSON.parse(JSON.stringify(conf || config)); + if (!N) N = c.totalCopayers; + + if (netKey) c.netKey = netKey; + var mainPrivateKey = new copay.PrivateKey({ + networkName: config.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.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; + + 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 cachedW = null; + var cachedWobj = null; + var cachedCreateW = function() { + if (!cachedW) { + cachedW = createW(); + cachedWobj = cachedW.toObj(); + cachedWobj.opts.reconnectDelay = 100; + } + var w = Wallet.fromObj(cachedWobj, cachedW.storage, cachedW.network, cachedW.blockchain); + return w; + }; + + 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 netKey = 'T0FbU2JLby0='; + var w = createW(netKey, 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() : null); + } else { + pkr.addCopayer(); + } + } + + 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; + }; + + it('should start the example server', function(done) { + startServer(function(err, s) { + if (err) return done(err); + server = s; + server.uri = 'https://localhost:8080/-'; + done(); + }); + }); + + it('#create a payment transaction', function() { + var w = cachedCreateW2(); + var comment = 'This is a comment'; + + unspentTest[0].address = w.publicKeyRing.getAddress(1, true, w.publicKey).toString(); + unspentTest[0].scriptPubKey = w.publicKeyRing.getScriptPubKeyHex(1, true, w.publicKey); + + var ntxid = w.createTxSync( + 'mgGJEugdPnvhmRuFdbdQcFfoFLc1XXeB79', + '123456789', + comment, + unspentTest + ); + + var t = w.txProposals; + var txp = t.txps[ntxid]; + var tx = txp.builder.build(); + should.exist(tx); + txp.comment.should.equal(comment); + }); + + it('should send a payment request', function(done) { + var w = cachedCreateW2(); + should.exist(w); + 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, []); + }, 1); + }; + var address = server.uri + '/request'; + var commentText = 'Hello, server. I\'d like to make a payment.'; + w.createTx(address, commentText, function(ntxid, ca) { + if (w.totalCopayers > 1) { + should.exist(ntxid); + console.log('Sent TX proposal to other copayers:'); + console.log([ntxid, ca]); + server.close(); + done(); + } else { + console.log('Sending TX to merchant server:'); + console.log(ntxid); + w.sendTx(ntxid, function(txid, ca) { + should.exist(txid); + console.log('TX sent:'); + console.log([ntxid, ca]); + server.close(); + done(); + }); + } + }); + }); +}); From 5e885da18420a0a1a7c576b629839d5c52bba0d9 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Mon, 28 Jul 2014 18:46:42 -0700 Subject: [PATCH 013/178] paypro: clean up test. --- test/test.PayPro.js | 207 -------------------------------------------- 1 file changed, 207 deletions(-) diff --git a/test/test.PayPro.js b/test/test.PayPro.js index 709a4a505..678160e20 100644 --- a/test/test.PayPro.js +++ b/test/test.PayPro.js @@ -20,28 +20,6 @@ var TransactionBuilder = bitcore.TransactionBuilder; var Transaction = bitcore.Transaction; var Address = bitcore.Address; -var addCopayers = function(w) { - for (var i = 0; i < 4; i++) { - w.publicKeyRing.addCopayer(); - } -}; - - - -var chai = chai || require('chai'); -var should = chai.should(); - -var FakeStorage = require('./mocks/FakeLocalStorage'); -//var copay = copay || require('../copay'); -var sinon = require('sinon'); -var FakeNetwork = require('./mocks/FakeNetwork'); -var FakeBlockchain = require('./mocks/FakeBlockchain'); -var FakeStorage = require('./mocks/FakeStorage'); -var WalletFactory = require('../js/models/core/WalletFactory'); -var Passphrase = require('../js/models/core/Passphrase'); - - - var G = typeof window !== 'undefined' ? window : global; G.SSL_UNTRUSTED = true; @@ -73,47 +51,6 @@ if (!is_browser) { }; } -function startServer_() { - if (is_browser) { - return cb(null, { - close: function() { - ; - } - }); - } - - var spawn = require('child_process').spawn; - var path = require('path'); - - //var bc = path.dirname(require.resolve('bitcore/package.json')); - //var bc = path.dirname(require.resolve(__dirname + '/../node_modules/bitcore/package.json')); - var bc = path.dirname(require.resolve(__dirname + '/../../bitcore/package.json')); - - var options = { - cwd: process.cwd(), - env: process.env, - setsid: false, - customFds: [-1, -1, -1] - }; - - var ps = spawn('node', - [bc + '/examples/PayPro/index.js', '-p', '8080'], - options); - - ps.close = function() { - ps.once('error', function() { - server.kill('SIGKILL'); - }); - ps.kill('SIGTERM'); - }; - - process.on('exit', function() { - ps.close(); - }); - - return cb(null, ps); -} - function startServer(cb) { if (is_browser) { return cb(null, { @@ -133,152 +70,8 @@ function startServer(cb) { }); } -// var server = startServer(); -// server.uri = 'https://localhost:8080/-'; - var server; -/* -describe('PayPro (in Wallet) model', function() { - var config = { - Network: FakeNetwork, - Blockchain: FakeBlockchain, - Storage: FakeStorage, - wallet: { - // requiredCopayers: 3, - // totalCopayers: 5, - requiredCopayers: 1, - totalCopayers: 1, - spendUnconfirmed: 1, - reconnectDelay: 100, - }, - blockchain: { - host: 'test.insight.is', - port: 80 - }, - networkName: 'testnet', - passphrase: 'test', - storageObj: new FakeStorage(), - networkObj: new FakeNetwork(), - blockchainObj: new FakeBlockchain(), - }; - - beforeEach(function() { - config.storageObj.reset(); - }); - - it('should start the example server', function(done) { - startServer(function(err, s) { - if (err) return done(err); - server = s; - server.uri = 'https://localhost:8080/-'; - done(); - }); - }); - - it('should be able to create wallets', function(done) { - var wf = new WalletFactory(config, '0.0.1'); - var w = wf.create(); - - var unspentTest = [{ - 'address': null, - "scriptPubKey": null, - "txid": "2ac165fa7a3a2b535d106a0041c7568d03b531e58aeccdd3199d7289ab12cfc1", - "vout": 1, - "amount": 10, - "confirmations": 7 - - , hashToScriptMap: { - '2N6fdPg2QL7V36XKe7a8wkkA5HCy7fNYmZF': '5321027445ab3a935dce7aee1dadb0d103ed6147a0f83deb80474a04538b2c5bc4d5092102ab32ba51402a139873aeb919c738f5a945f3956f8f8c6ba296677bd29e85d7e821036f119b72e09f76c11ebe2cf754d64eac2cb42c9e623455d54aaa89d70c11f9c82103bcbd3f8ab2c849ea9eae434733cee8b75120d26233def56011b3682ca12081d72103f37f81dc534163b9f73ecf36b91e6c3fb8ae370c24618f91bb1d972e86ceeee255ae' - } - }]; - - unspentTest[0].address = w.publicKeyRing.getAddress(1, true, w.publicKey).toString(); - unspentTest[0].scriptPubKey = w.publicKeyRing.getScriptPubKeyHex(1, true, w.publicKey); - - if (0) - var unspentTest = [{ - address: '2N6fdPg2QL7V36XKe7a8wkkA5HCy7fNYmZF', - scriptPubKey: 'a91493372782bab70f4eefdefefea8ece0df44f9596887', - txid: '2ac165fa7a3a2b535d106a0041c7568d03b531e58aeccdd3199d7289ab12cfc1', - vout: 1, - amount: 10, - confirmations: 7, - scriptSig: ['00493046022100b8249a4fc326c4c33882e9d5468a1c6faa01e8c6cef0a24970122e804abdd860022100dbf6ee3b07d3aad8f73997e62ad20654a08aa63a7609792d02f3d5d088e69ad9014cad5321027445ab3a935dce7aee1dadb0d103ed6147a0f83deb80474a04538b2c5bc4d5092102ab32ba51402a139873aeb919c738f5a945f3956f8f8c6ba296677bd29e85d7e821036f119b72e09f76c11ebe2cf754d64eac2cb42c9e623455d54aaa89d70c11f9c82103bcbd3f8ab2c849ea9eae434733cee8b75120d26233def56011b3682ca12081d72103f37f81dc534163b9f73ecf36b91e6c3fb8ae370c24618f91bb1d972e86ceeee255ae'], - hashToScriptMap: { - '2N6fdPg2QL7V36XKe7a8wkkA5HCy7fNYmZF': '5321027445ab3a935dce7aee1dadb0d103ed6147a0f83deb80474a04538b2c5bc4d5092102ab32ba51402a139873aeb919c738f5a945f3956f8f8c6ba296677bd29e85d7e821036f119b72e09f76c11ebe2cf754d64eac2cb42c9e623455d54aaa89d70c11f9c82103bcbd3f8ab2c849ea9eae434733cee8b75120d26233def56011b3682ca12081d72103f37f81dc534163b9f73ecf36b91e6c3fb8ae370c24618f91bb1d972e86ceeee255ae' - } - }]; - - // Addresses - var addrs = [ - 'mzTQ66VKcybz9BD1LAqEwMFp9NrBGS82sY', - 'mmu9k3KzsDMEm9JxmJmZaLhovAoRKW3zr4', - 'myqss64GNZuWuFyg5LTaoTCyWEpKH56Fgz' - ]; - - // Private keys in WIF format (see TransactionBuilder.js for other options) - var keys = [ - 'cVvr5YmWVAkVeZWAawd2djwXM4QvNuwMdCw1vFQZBM1SPFrtE8W8', - 'cPyx1hXbe3cGQcHZbW3GNSshCYZCriidQ7afR2EBsV6ReiYhSkNF' - // 'cUB9quDzq1Bj7pocenmofzNQnb1wJNZ5V3cua6pWKzNL1eQtaDqQ' - ]; - - var unspent = [{ - // http://blockexplorer.com/testnet/rawtx/1fcfe898cc2612f8b222bd3b4ac8d68bf95d43df8367b71978c184dea35bde22 - 'txid': '1fcfe898cc2612f8b222bd3b4ac8d68bf95d43df8367b71978c184dea35bde22', - 'vout': 1, - 'address': addrs[0], - 'scriptPubKey': '76a94c14cfbe41f4a518edc25af71bafc72fb61bfcfc4fcd88ac', - 'amount': 1.60000000, - 'confirmations': 9 - }, - - { - // http://blockexplorer.com/testnet/rawtx/0624c0c794447b0d2343ae3d20382983f41b915bb115a834419e679b2b13b804 - 'txid': '0624c0c794447b0d2343ae3d20382983f41b915bb115a834419e679b2b13b804', - 'vout': 1, - 'address': addrs[1], - 'scriptPubKey': '76a94c14460376539c219c5e3274d86f16b40e806b37817688ac', - 'amount': 1.60000000, - 'confirmations': 9 - } - ]; - - - should.exist(w); - - w.getUnspent = function(cb) { - return setTimeout(function() { - return cb(null, unspentTest, []); - }, 1); - }; - - var address = server.uri + '/request'; - var commentText = 'Hello, server. I\'d like to make a payment.'; - w.createTx(address, commentText, function(ntxid, ca) { - if (w.totalCopayers > 1) { - should.exist(ntxid); - console.log('Sent TX proposal to other copayers:'); - console.log([ntxid, ca]); - server.close(); - done(); - } else { - console.log('Sending TX to merchant server:'); - console.log(ntxid); - w.sendTx(ntxid, function(txid, ca) { - should.exist(txid); - console.log('TX sent:'); - console.log([ntxid, ca]); - server.close(); - done(); - }); - } - }); - }); -}); -*/ - describe('PayPro (in Wallet) model', function() { var config = { // requiredCopayers: 3, From 283dec1f2e2e0b8440106d1bfa112ff97164056f Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Mon, 28 Jul 2014 18:48:59 -0700 Subject: [PATCH 014/178] paypro: clean up test again. --- test/test.PayPro.js | 25 ++----------------------- 1 file changed, 2 insertions(+), 23 deletions(-) diff --git a/test/test.PayPro.js b/test/test.PayPro.js index 678160e20..56a226a1e 100644 --- a/test/test.PayPro.js +++ b/test/test.PayPro.js @@ -187,7 +187,7 @@ describe('PayPro (in Wallet) model', function() { return w; }; - it('should start the example server', function(done) { + it('#start the example server', function(done) { startServer(function(err, s) { if (err) return done(err); server = s; @@ -196,28 +196,7 @@ describe('PayPro (in Wallet) model', function() { }); }); - it('#create a payment transaction', function() { - var w = cachedCreateW2(); - var comment = 'This is a comment'; - - unspentTest[0].address = w.publicKeyRing.getAddress(1, true, w.publicKey).toString(); - unspentTest[0].scriptPubKey = w.publicKeyRing.getScriptPubKeyHex(1, true, w.publicKey); - - var ntxid = w.createTxSync( - 'mgGJEugdPnvhmRuFdbdQcFfoFLc1XXeB79', - '123456789', - comment, - unspentTest - ); - - var t = w.txProposals; - var txp = t.txps[ntxid]; - var tx = txp.builder.build(); - should.exist(tx); - txp.comment.should.equal(comment); - }); - - it('should send a payment request', function(done) { + it('#send a payment request', function(done) { var w = cachedCreateW2(); should.exist(w); unspentTest[0].address = w.publicKeyRing.getAddress(1, true, w.publicKey).toString(); From e0ff9ca6bdebb4091add7f13cade0b76a2c1c9f7 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Tue, 29 Jul 2014 11:14:07 -0700 Subject: [PATCH 015/178] paypro: fix parseBitcoinURI. start using bitcoin uris in tests. --- js/controllers/send.js | 6 ++++-- js/models/core/Wallet.js | 8 ++++++++ test/test.PayPro.js | 11 +++++++++-- 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/js/controllers/send.js b/js/controllers/send.js index 05d6e3596..31877c2da 100644 --- a/js/controllers/send.js +++ b/js/controllers/send.js @@ -86,8 +86,10 @@ angular.module('copayApp.controllers').controller('SendController', $rootScope.pendingPayment = null; } - if (~address.indexOf('://')) { - w.createTx(address, commentText, done); + var uri = address.indexOf('bitcoin:') === 0 + && copay.Structure.parseBitcoinURI(address); + if (uri && uri.merchant) { + w.createTx(uri.merchant, commentText, done); } else { w.createTx(address, amount, commentText, done); } diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index f5b33ca77..88e816b45 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -5,6 +5,7 @@ var http = require('http'); var EventEmitter = imports.EventEmitter || require('events').EventEmitter; var async = require('async'); var preconditions = require('preconditions').singleton(); +var parseBitcoinURI = require('./Structure').parseBitcoinURI; var bitcore = require('bitcore'); var bignum = bitcore.Bignum; @@ -774,6 +775,13 @@ Wallet.prototype.createPaymentTx = function(options, cb) { options = { uri: options }; } + if (options.uri.indexOf('bitcoin:') === 0) { + options.uri = parseBitcoinURI(options.uri).merchant; + if (!options.uri) { + return cb(new Error('No URI.')); + } + } + return $http({ method: options.method || 'POST', url: options.uri || options.url, diff --git a/test/test.PayPro.js b/test/test.PayPro.js index 56a226a1e..04ac728da 100644 --- a/test/test.PayPro.js +++ b/test/test.PayPro.js @@ -37,6 +37,12 @@ if (!is_browser) { error: function(cb) { this._error = cb; return this; + }, + _success: function() { + ; + }, + _error: function(_, err) { + throw err; } }; if (options.responseType === 'arraybuffer') { @@ -44,7 +50,7 @@ if (!is_browser) { options.encoding = null; } _request(options, function(err, res, body) { - if (err) return ret._error(null, null, null, options); + if (err) return ret._error(null, err, null, options); return ret._success(body, res.statusCode, res.headers, options); }); return ret; @@ -62,6 +68,7 @@ function startServer(cb) { var path = require('path'); var bc = path.dirname(require.resolve(__dirname + '/../../bitcore/package.json')); + //var bc = path.dirname(require.resolve('bitcore/package.json')); var example = bc + '/examples/PayPro/server.js'; var server = require(example); @@ -206,7 +213,7 @@ describe('PayPro (in Wallet) model', function() { return cb(null, unspentTest, []); }, 1); }; - var address = server.uri + '/request'; + var address = 'bitcoin:mq7se9wy2egettFxPbmn99cK8v5AFq55Lx?amount=0.11&r=' + server.uri + '/request'; var commentText = 'Hello, server. I\'d like to make a payment.'; w.createTx(address, commentText, function(ntxid, ca) { if (w.totalCopayers > 1) { From 8d96b446df95293c765945a3926c69451753373c Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Tue, 29 Jul 2014 11:52:59 -0700 Subject: [PATCH 016/178] paypro: fix some more errors. --- js/models/core/Wallet.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index 88e816b45..fc0034e81 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -979,7 +979,6 @@ Wallet.prototype.sendPaymentTx = function(ntxid, options, cb) { responseType: 'arraybuffer' }) .success(function(data, status, headers, config) { - if (err) return cb(err); data = PayPro.PaymentACK.decode(data); var ack = new PayPro(); ack = ack.makePaymentACK(data); @@ -1007,7 +1006,7 @@ Wallet.prototype.receivePaymentRequestACK = function(tx, txp, ack, cb) { tx = ptx; } var txid = tx.getHash().toString('hex'); - return cb(txid, txp.pr.ca); + return cb(txid, txp.merchant.pr.ca); }; Wallet.prototype.createPaymentTxSync = function(options, merchantData, unspent) { From 9d81cc0006d13aa34d9b9f6b47779356fba4320e Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Tue, 29 Jul 2014 11:55:24 -0700 Subject: [PATCH 017/178] paypro: start mocking up tests for running in browser and node. --- test/test.PayPro.js | 314 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 313 insertions(+), 1 deletion(-) diff --git a/test/test.PayPro.js b/test/test.PayPro.js index 04ac728da..2a2bd85f0 100644 --- a/test/test.PayPro.js +++ b/test/test.PayPro.js @@ -19,6 +19,7 @@ var bitcore = bitcore || require('bitcore'); var TransactionBuilder = bitcore.TransactionBuilder; var Transaction = bitcore.Transaction; var Address = bitcore.Address; +var PayPro = bitcore.PayPro; var G = typeof window !== 'undefined' ? window : global; G.SSL_UNTRUSTED = true; @@ -57,7 +58,7 @@ if (!is_browser) { }; } -function startServer(cb) { +function startServer_(cb) { if (is_browser) { return cb(null, { close: function() { @@ -77,6 +78,317 @@ function startServer(cb) { }); } +var x509 = { + priv: '' + + 'LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcEFJQkFBS0NBUUVBeFRKdUsyYUdM' + + 'bjFkWEpLRGg0TXdQTFVrbDNISTVwR25HNWFjNGwvMGlobXE4Y3dDCitGVlBnWk1TNTlheWtpc0Ir' + + 'ekM3dnR2a0prL2J2K0JTT1g3b3hkSXN1TDNkS1FGcHVYWFZmcmRiOTV3WW40TSsKL25qRWhYTWxo' + + 'Vk1IL09DaUFnOUpLaFRLV0w2R1JXWkFBaEE3bEJSaGdTTkRUaVRDNTFDYmlLN3hBNnBONCt0UQpI' + + 'eG9tSlBYclpSa2JCMmtsT2ZXd2J2OTNZM0oxS0ZEK2kwUE1RSEx3N3JoRXVteEM5MytISFVWWVZI' + + 'N0gxVFBaCkgxYmRVSkowMmdRZXlsSnNzWUNKeWRaUHpOVC96dXRzL0tKV2RSdjVseHdHOXU5dE1O' + + 'TWdoSmJtQWFNa01HaSsKbzdQTkV5UDNxSEZyWXBZaHM1cHFMSE1STkI3OFFNOUllTmpMRndJREFR' + + 'QUJBb0lCQVFERVJyalBiQUdjbmwxaAorZGIrOTczNGZ0aElBUkpWSko1dTRFK1JKcThSRWhGTEVL' + + 'UFlKNW0yUC94dVZBMXpYV2xnYXhaRUZ6d1VRaUpZCjdsOEpLVjlwSHhReVlaQ1M4dndYZzhpWGtz' + + 'dndQaWRvQmN1YW4vd0RWQ1FCZXk2VkxjVXpSYUd1Ui9sTHNYK1YKN2Z0QjBvUnFsSXFrYmNQZE1N' + + 'dnFUeG93UnVoUG11Q3JWVGpPNHBiTnFuU09OUExPaUovRkFYYjJwZnpGZnBCUgpHeCtFTW16d2Ur' + + 'SEZuSkJHRGhIWjk5bm4vVEJmYUp6TlZDcURZLzNid3o1WDdIUU5ZN1QrSnlUVUZzZVE5NHhzCnpy' + + 'a2lidGRmVGNUanB1K1VoWm80c1p6Q3IrZkhHWm9FOUdEUHF0ZDRnQ3ByazRFS0pzbXFCRVN4QlhT' + + 'RGhZZ04KOXBVRDM4c1pBb0dCQU9yZkRqdDZaL0ZDamFuVThXek5GaWYrOVQxQTJ4b013RDVWU2xN' + + 'dVJyWW1HbGZyMEM5TQpmMUVvZ2l2dVRrYnA3cmtnZFRhWVRTYndmTnFaQkt4Y3R5YzdCaGRwWnhE' + + 'RVdKa2Z5cThxVngvem1Cek1JK1ZzCjJLYi9hcHZXcmJlb3NET0NyeUg1YzhKc1VUOXhUWDNYYnhF' + + 'anlPSlFCU1lHRE1qUHlKNkU5czZMQW9HQkFOYnYKd2d0S2Nra0tLbDJhNXZzaGR2RENnNnFLL1Fn' + + 'T20vNktUSlVKRVNqaHoydFIrZlBWUjcwVEg5UmhoVFJscERXQgpCd3oyU2NCc1RRNDIvTGsxRnky' + + 'MFQvck12S3VmSEw1VE1BNGZ6NWRxMUxIbmN6ejZVazVnWEtBT09rUjlVdVhpClR0eTNoREcyQkM4' + + 'Nk1LTVJ4SjUxRWJxam94d0VSMTAwU2FuTVBmTWxBb0dBSUhLY1pyOHNhUHBHMC9XbFBPREEKZE5v' + + 'V1MxWVFidkxnQkR5SVBpR2doejJRV2lFcjY3em53ZkNVdXpqNiszVUtFKzFXQkNyYVRjemZrdHVj' + + 'OTZyLwphcDRPNDJFZWFnU1dNT0ZoZ1AyYWQ4R1JmRGovcEl4N0NlY3pkVUFkVThnc1A1R0lYR3M0' + + 'QU40eUEwL0Y0dUxHCloxbklRT3ZKS2syZnFvWjZNdHd2dEswQ2dZRUFnSjdGTGVDRTkzUmYyZGdD' + + 'ZFRHWGJZZlpKc3M1bEFLNkV0NUwKNmJ1ZFN5dWw1Z0VPWkgyekNsQlJjZFJSMUFNbSt1V1ZoSW8x' + + 'cERLckFlQ2g1MnIvemRmakxLQXNIejkrQWQ3aQpHUEdzVmw0Vm5jaDFTMzQ0bHJKUGUzQklLZ2dj' + + 'L1hncDNTYnNzcHJMY2orT0wyZElrOUpXbzZ1Y3hmMUJmMkwwCjJlbGhBUWtDZ1lCWHN5elZWL1pK' + + 'cVhOcFdDZzU1TDNVRm9UTHlLU3FsVktNM1dpRzVCS240QWF6VkNITCtHUVUKeHd4U2dSOWZRNElu' + + 'dStyUHJOM0lteWswbEtQR0Y5U3pDUlJUaUpGUjcyc05xbE82bDBWOENXUkFQVFBKY2dxVgoxVThO' + + 'SEs4YjNaaUlvR0orbXNOenBkeHJqNjJIM0E2K1krQXNOWTRTbVVUWEg5eWpnK251a2c9PQotLS0t' + + 'LUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo=', + pub: '' + + 'LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FR' + + 'OEFNSUlCQ2dLQ0FRRUF4VEp1SzJhR0xuMWRYSktEaDRNdwpQTFVrbDNISTVwR25HNWFjNGwvMGlo' + + 'bXE4Y3dDK0ZWUGdaTVM1OWF5a2lzQit6Qzd2dHZrSmsvYnYrQlNPWDdvCnhkSXN1TDNkS1FGcHVY' + + 'WFZmcmRiOTV3WW40TSsvbmpFaFhNbGhWTUgvT0NpQWc5SktoVEtXTDZHUldaQUFoQTcKbEJSaGdT' + + 'TkRUaVRDNTFDYmlLN3hBNnBONCt0UUh4b21KUFhyWlJrYkIya2xPZld3YnY5M1kzSjFLRkQraTBQ' + + 'TQpRSEx3N3JoRXVteEM5MytISFVWWVZIN0gxVFBaSDFiZFVKSjAyZ1FleWxKc3NZQ0p5ZFpQek5U' + + 'L3p1dHMvS0pXCmRSdjVseHdHOXU5dE1OTWdoSmJtQWFNa01HaStvN1BORXlQM3FIRnJZcFloczVw' + + 'cUxITVJOQjc4UU05SWVOakwKRndJREFRQUIKLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tCg==', + der: '' + + 'MIIDBjCCAe4CCQDI2qWdA3/VpDANBgkqhkiG9w0BAQUFADBFMQswCQYDVQQGEwJBVTETMBEGA1UE' + + 'CAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMB4XDTE0MDcx' + + 'NjAxMzM1MVoXDTE1MDcxNjAxMzM1MVowRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3Rh' + + 'dGUxITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDCCASIwDQYJKoZIhvcNAQEBBQAD' + + 'ggEPADCCAQoCggEBAMUybitmhi59XVySg4eDMDy1JJdxyOaRpxuWnOJf9IoZqvHMAvhVT4GTEufW' + + 'spIrAfswu77b5CZP27/gUjl+6MXSLLi93SkBabl11X63W/ecGJ+DPv54xIVzJYVTB/zgogIPSSoU' + + 'yli+hkVmQAIQO5QUYYEjQ04kwudQm4iu8QOqTePrUB8aJiT162UZGwdpJTn1sG7/d2NydShQ/otD' + + 'zEBy8O64RLpsQvd/hx1FWFR+x9Uz2R9W3VCSdNoEHspSbLGAicnWT8zU/87rbPyiVnUb+ZccBvbv' + + 'bTDTIISW5gGjJDBovqOzzRMj96hxa2KWIbOaaixzETQe/EDPSHjYyxcCAwEAATANBgkqhkiG9w0B' + + 'AQUFAAOCAQEAL6AMMfC3TlRcmsIgHxjVD4XYtISlldnrn2X9zvFbJKCpNy8XQQosQxrhyfzPHQKj' + + 'lS2L/KCGMnjx9QkYD2Hlp1MJ1uVv9888th/gcZOv3Or3hQyi5K1Sh5xCG+69lUOqUEGu9B4irsqo' + + 'FomQVbQolSy+t4apdJi7kuEDwFDk4gZiVEfsuX+naN5a6pCnWnhX1Vf4fKwfkLobKKXm2zQVsjxl' + + 'wBAqOEmJGDLoRMXH56qJnEZ/dqsczaJOHQSi9mFEHL0r5rsEDTT5AVxdnBfNnyGaCH7/zANEko+F' + + 'GBj1JdJaJgFTXdbxDoyoPTPD+LJqSK5XYToo46y/T0u9CLveNA==', + pem: '' + + 'LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURCakNDQWU0Q0NRREkycVdkQTMvVnBEQU5C' + + 'Z2txaGtpRzl3MEJBUVVGQURCRk1Rc3dDUVlEVlFRR0V3SkIKVlRFVE1CRUdBMVVFQ0F3S1UyOXRa' + + 'UzFUZEdGMFpURWhNQjhHQTFVRUNnd1lTVzUwWlhKdVpYUWdWMmxrWjJsMApjeUJRZEhrZ1RIUmtN' + + 'QjRYRFRFME1EY3hOakF4TXpNMU1Wb1hEVEUxTURjeE5qQXhNek0xTVZvd1JURUxNQWtHCkExVUVC' + + 'aE1DUVZVeEV6QVJCZ05WQkFnTUNsTnZiV1V0VTNSaGRHVXhJVEFmQmdOVkJBb01HRWx1ZEdWeWJt' + + 'VjAKSUZkcFpHZHBkSE1nVUhSNUlFeDBaRENDQVNJd0RRWUpLb1pJaHZjTkFRRUJCUUFEZ2dFUEFE' + + 'Q0NBUW9DZ2dFQgpBTVV5Yml0bWhpNTlYVnlTZzRlRE1EeTFKSmR4eU9hUnB4dVduT0pmOUlvWnF2' + + 'SE1BdmhWVDRHVEV1ZldzcElyCkFmc3d1NzdiNUNaUDI3L2dVamwrNk1YU0xMaTkzU2tCYWJsMTFY' + + 'NjNXL2VjR0orRFB2NTR4SVZ6SllWVEIvemcKb2dJUFNTb1V5bGkraGtWbVFBSVFPNVFVWVlFalEw' + + 'NGt3dWRRbTRpdThRT3FUZVByVUI4YUppVDE2MlVaR3dkcApKVG4xc0c3L2QyTnlkU2hRL290RHpF' + + 'Qnk4TzY0Ukxwc1F2ZC9oeDFGV0ZSK3g5VXoyUjlXM1ZDU2ROb0VIc3BTCmJMR0FpY25XVDh6VS84' + + 'N3JiUHlpVm5VYitaY2NCdmJ2YlREVElJU1c1Z0dqSkRCb3ZxT3p6Uk1qOTZoeGEyS1cKSWJPYWFp' + + 'eHpFVFFlL0VEUFNIall5eGNDQXdFQUFUQU5CZ2txaGtpRzl3MEJBUVVGQUFPQ0FRRUFMNkFNTWZD' + + 'MwpUbFJjbXNJZ0h4alZENFhZdElTbGxkbnJuMlg5enZGYkpLQ3BOeThYUVFvc1F4cmh5ZnpQSFFL' + + 'amxTMkwvS0NHCk1uang5UWtZRDJIbHAxTUoxdVZ2OTg4OHRoL2djWk92M09yM2hReWk1SzFTaDV4' + + 'Q0crNjlsVU9xVUVHdTlCNGkKcnNxb0ZvbVFWYlFvbFN5K3Q0YXBkSmk3a3VFRHdGRGs0Z1ppVkVm' + + 'c3VYK25hTjVhNnBDblduaFgxVmY0Zkt3ZgprTG9iS0tYbTJ6UVZzanhsd0JBcU9FbUpHRExvUk1Y' + + 'SDU2cUpuRVovZHFzY3phSk9IUVNpOW1GRUhMMHI1cnNFCkRUVDVBVnhkbkJmTm55R2FDSDcvekFO' + + 'RWtvK0ZHQmoxSmRKYUpnRlRYZGJ4RG95b1BUUEQrTEpxU0s1WFlUb28KNDZ5L1QwdTlDTHZlTkE9' + + 'PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==' +}; + +x509.priv = new Buffer(x509.priv, 'base64'); +x509.pub = new Buffer(x509.pub, 'base64'); +x509.der = new Buffer(x509.der, 'base64'); +x509.pem = new Buffer(x509.pem, 'base64'); + +function startServer(cb) { + var server = { + POST: { + + /** + * Receive "I want to pay" + */ + + '/-/request': function(req, cb) { + var res = { + statusCode: 200, + headers: {}, + body: {} + }; + + var uid = 0; + + console.log('Received payment "request" from %s.', req.socket.remoteAddress); + + var outputs = []; + + [2000, 1000].forEach(function(value) { + var po = new PayPro(); + po = po.makeOutput(); + // number of satoshis to be paid + po.set('amount', value); + // a TxOut script where the payment should be sent. similar to OP_CHECKSIG + po.set('script', new Buffer([ + 118, // OP_DUP + 169, // OP_HASH160 + 76, // OP_PUSHDATA1 + 20, // number of bytes + 0xcf, + 0xbe, + 0x41, + 0xf4, + 0xa5, + 0x18, + 0xed, + 0xc2, + 0x5a, + 0xf7, + 0x1b, + 0xaf, + 0xc7, + 0x2f, + 0xb6, + 0x1b, + 0xfc, + 0xfc, + 0x4f, + 0xcd, + 136, // OP_EQUALVERIFY + 172 // OP_CHECKSIG + ])); + outputs.push(po.message); + }); + + /** + * Payment Details + */ + + var mdata = new Buffer([0]); + uid++; + if (uid > 0xffff) { + throw new Error('UIDs bigger than 0xffff not supported.'); + } else if (uid > 0xff) { + mdata = new Buffer([(uid >> 8) & 0xff, (uid >> 0) & 0xff]) + } else { + mdata = new Buffer([0, uid]) + } + var now = Date.now() / 1000 | 0; + var pd = new PayPro(); + pd = pd.makePaymentDetails(); + pd.set('network', 'test'); + pd.set('outputs', outputs); + pd.set('time', now); + pd.set('expires', now * 60 * 60 * 24); + pd.set('memo', 'Hello, this is the server, we would like some money.'); + var port = +req.headers.host.split(':')[1] || server.port; + pd.set('payment_url', 'https://localhost:' + port + '/-/pay'); + pd.set('merchant_data', mdata); + + /* + * PaymentRequest + */ + + var cr = new PayPro(); + cr = cr.makeX509Certificates(); + cr.set('certificate', [x509.der]); + + // We send the PaymentRequest to the customer + var pr = new PayPro(); + pr = pr.makePaymentRequest(); + pr.set('payment_details_version', 1); + pr.set('pki_type', 'x509+sha256'); + pr.set('pki_data', cr.serialize()); + pr.set('serialized_payment_details', pd.serialize()); + pr.sign(x509.priv); + + pr = pr.serialize(); + + // BIP-71 - set the content-type + res.headers['Content-Type'] = PayPro.PAYMENT_REQUEST_CONTENT_TYPE; + res.headers['Content-Length'] = pr.length + ''; + res.headers['Content-Transfer-Encoding'] = 'binary'; + + res.body = pr; + + return cb(null, res, res.body); + }, + + /** + * Receive Payment + */ + + '/-/pay': function(req, cb) { + var body = req.body; + + var res = { + statusCode: 200, + headers: {}, + body: {} + }; + + body = PayPro.Payment.decode(body); + + var pay = new PayPro(); + pay = pay.makePayment(body); + var merchant_data = pay.get('merchant_data'); + var transactions = pay.get('transactions'); + var refund_to = pay.get('refund_to'); + var memo = pay.get('memo'); + + console.log('Received payment from %s.', req.socket.remoteAddress); + console.log('Customer Message: %s', memo); + console.log('Payment Message:'); + console.log(pay); + + // We send this to the customer after receiving a Payment + // Then we propogate the transaction through bitcoin network + var ack = new PayPro(); + ack = ack.makePaymentACK(); + ack.set('payment', pay.message); + ack.set('memo', 'Thank you for your payment!'); + + ack = ack.serialize(); + + // BIP-71 - set the content-type + res.headers['Content-Type'] = PayPro.PAYMENT_ACK_CONTENT_TYPE; + res.headers['Content-Length'] = ack.length + ''; + res.headers['Content-Transfer-Encoding'] = 'binary'; + + transactions = transactions.map(function(tx) { + tx.buffer = tx.buffer.slice(tx.offset, tx.limit); + var ptx = new bitcore.Transaction(); + ptx.parse(tx.buffer); + return ptx; + }); + + transactions.forEach(function(tx) { + var id = tx.getHash().toString('hex'); + console.log(''); + console.log('Sending transaction with txid: %s', id); + console.log(tx.getStandardizedObject()); + }); + + res.body = ack; + + return cb(null, res, res.body); + } + }, + listen: function() { + }, + close: function() { + } + }; + + G.$http = function(options) { + var ret = { + success: function(cb) { + this._success = cb; + return this; + }, + error: function(cb) { + this._error = cb; + return this; + }, + _success: function() { + ; + }, + _error: function(_, err) { + throw err; + } + }; + var method = (options.method || 'GET').toUpperCase(); + var uri = options.uri || options.url; + var path = uri.replace(/^https?:\/\/[^\/]+/, ''); + var req = options; + req.headers = req.headers || {}; + req.body = req.body || {}; + req.socket = { + remoteAddress: 'localhost' + }; + req.headers['Host'] = 'localhost:8080'; + Object.keys(req.headers).forEach(function(key) { + req.headers[key] = req.headers[key] + ''; + req.headers[key.toLowerCase()] = req.headers[key] + ''; + }); + setTimeout(function() { + server[method][path](req, function(err, res, body) { + if (err) return ret._error(null, err, null, options); + Object.keys(res.headers).forEach(function(key) { + res.headers[key] = res.headers[key] + ''; + res.headers[key.toLowerCase()] = res.headers[key] + ''; + }); + return ret._success(body, res.statusCode, res.headers, options); + }); + }, 1); + return ret; + }; + + setTimeout(function() { + return cb(null, server); + }, 1); +} + var server; describe('PayPro (in Wallet) model', function() { From 96c02c1e37c4d9bc1efa18e026279f3cd1892cb6 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Tue, 29 Jul 2014 11:56:25 -0700 Subject: [PATCH 018/178] paypro: clean up tests. --- test/test.PayPro.js | 56 +-------------------------------------------- 1 file changed, 1 insertion(+), 55 deletions(-) diff --git a/test/test.PayPro.js b/test/test.PayPro.js index 2a2bd85f0..3840a8f54 100644 --- a/test/test.PayPro.js +++ b/test/test.PayPro.js @@ -21,63 +21,9 @@ var Transaction = bitcore.Transaction; var Address = bitcore.Address; var PayPro = bitcore.PayPro; -var G = typeof window !== 'undefined' ? window : global; +var G = is_browser ? window : global; G.SSL_UNTRUSTED = true; -if (!is_browser) { - // Disable strictSSL - process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = '0'; - - var _request = require('request'); - G.$http = function(options) { - var ret = { - success: function(cb) { - this._success = cb; - return this; - }, - error: function(cb) { - this._error = cb; - return this; - }, - _success: function() { - ; - }, - _error: function(_, err) { - throw err; - } - }; - if (options.responseType === 'arraybuffer') { - delete options.responseType; - options.encoding = null; - } - _request(options, function(err, res, body) { - if (err) return ret._error(null, err, null, options); - return ret._success(body, res.statusCode, res.headers, options); - }); - return ret; - }; -} - -function startServer_(cb) { - if (is_browser) { - return cb(null, { - close: function() { - ; - } - }); - } - - var path = require('path'); - var bc = path.dirname(require.resolve(__dirname + '/../../bitcore/package.json')); - //var bc = path.dirname(require.resolve('bitcore/package.json')); - var example = bc + '/examples/PayPro/server.js'; - var server = require(example); - - server.listen(8080, function(addr) { - return cb(null, server); - }); -} - var x509 = { priv: '' + 'LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcEFJQkFBS0NBUUVBeFRKdUsyYUdM' From d2e7e35e9fd25de9a3de6ca2656cbc9fae0a14b5 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Tue, 29 Jul 2014 12:00:09 -0700 Subject: [PATCH 019/178] paypro: test/minor - use paypro content type constant. --- js/models/core/Wallet.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index fc0034e81..3a6ce5bbe 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -971,7 +971,7 @@ Wallet.prototype.sendPaymentTx = function(ntxid, options, cb) { // BIP-71 'Accept': PayPro.PAYMENT_REQUEST_CONTENT_TYPE + ', ' + PayPro.PAYMENT_ACK_CONTENT_TYPE, - 'Content-Type': 'application/bitcoin-payment', + 'Content-Type': PayPro.PAYMENT_CONTENT_TYPE, 'Content-Length': pay.length + '', 'Content-Transfer-Encoding': 'binary' }, From 4cbc9ebf639caaec36ae3ab08c1e3abadaf87e74 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Tue, 29 Jul 2014 17:24:23 -0700 Subject: [PATCH 020/178] paypro: test - add mock paypro server. --- test/mocks/FakePayProServer.js | 331 +++++++++++++++++++++++++++++++++ test/test.PayPro.js | 311 ------------------------------- 2 files changed, 331 insertions(+), 311 deletions(-) create mode 100644 test/mocks/FakePayProServer.js diff --git a/test/mocks/FakePayProServer.js b/test/mocks/FakePayProServer.js new file mode 100644 index 000000000..5837af4bb --- /dev/null +++ b/test/mocks/FakePayProServer.js @@ -0,0 +1,331 @@ +'use strict'; + +var is_browser = typeof process == 'undefined' + || typeof process.versions === 'undefined'; +var bitcore = bitcore || require('bitcore'); +var PayPro = bitcore.PayPro; + +var G = is_browser ? window : global; +G.SSL_UNTRUSTED = true; + +var x509 = { + priv: '' + + 'LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcEFJQkFBS0NBUUVBeFRKdUsyYUdM' + + 'bjFkWEpLRGg0TXdQTFVrbDNISTVwR25HNWFjNGwvMGlobXE4Y3dDCitGVlBnWk1TNTlheWtpc0Ir' + + 'ekM3dnR2a0prL2J2K0JTT1g3b3hkSXN1TDNkS1FGcHVYWFZmcmRiOTV3WW40TSsKL25qRWhYTWxo' + + 'Vk1IL09DaUFnOUpLaFRLV0w2R1JXWkFBaEE3bEJSaGdTTkRUaVRDNTFDYmlLN3hBNnBONCt0UQpI' + + 'eG9tSlBYclpSa2JCMmtsT2ZXd2J2OTNZM0oxS0ZEK2kwUE1RSEx3N3JoRXVteEM5MytISFVWWVZI' + + 'N0gxVFBaCkgxYmRVSkowMmdRZXlsSnNzWUNKeWRaUHpOVC96dXRzL0tKV2RSdjVseHdHOXU5dE1O' + + 'TWdoSmJtQWFNa01HaSsKbzdQTkV5UDNxSEZyWXBZaHM1cHFMSE1STkI3OFFNOUllTmpMRndJREFR' + + 'QUJBb0lCQVFERVJyalBiQUdjbmwxaAorZGIrOTczNGZ0aElBUkpWSko1dTRFK1JKcThSRWhGTEVL' + + 'UFlKNW0yUC94dVZBMXpYV2xnYXhaRUZ6d1VRaUpZCjdsOEpLVjlwSHhReVlaQ1M4dndYZzhpWGtz' + + 'dndQaWRvQmN1YW4vd0RWQ1FCZXk2VkxjVXpSYUd1Ui9sTHNYK1YKN2Z0QjBvUnFsSXFrYmNQZE1N' + + 'dnFUeG93UnVoUG11Q3JWVGpPNHBiTnFuU09OUExPaUovRkFYYjJwZnpGZnBCUgpHeCtFTW16d2Ur' + + 'SEZuSkJHRGhIWjk5bm4vVEJmYUp6TlZDcURZLzNid3o1WDdIUU5ZN1QrSnlUVUZzZVE5NHhzCnpy' + + 'a2lidGRmVGNUanB1K1VoWm80c1p6Q3IrZkhHWm9FOUdEUHF0ZDRnQ3ByazRFS0pzbXFCRVN4QlhT' + + 'RGhZZ04KOXBVRDM4c1pBb0dCQU9yZkRqdDZaL0ZDamFuVThXek5GaWYrOVQxQTJ4b013RDVWU2xN' + + 'dVJyWW1HbGZyMEM5TQpmMUVvZ2l2dVRrYnA3cmtnZFRhWVRTYndmTnFaQkt4Y3R5YzdCaGRwWnhE' + + 'RVdKa2Z5cThxVngvem1Cek1JK1ZzCjJLYi9hcHZXcmJlb3NET0NyeUg1YzhKc1VUOXhUWDNYYnhF' + + 'anlPSlFCU1lHRE1qUHlKNkU5czZMQW9HQkFOYnYKd2d0S2Nra0tLbDJhNXZzaGR2RENnNnFLL1Fn' + + 'T20vNktUSlVKRVNqaHoydFIrZlBWUjcwVEg5UmhoVFJscERXQgpCd3oyU2NCc1RRNDIvTGsxRnky' + + 'MFQvck12S3VmSEw1VE1BNGZ6NWRxMUxIbmN6ejZVazVnWEtBT09rUjlVdVhpClR0eTNoREcyQkM4' + + 'Nk1LTVJ4SjUxRWJxam94d0VSMTAwU2FuTVBmTWxBb0dBSUhLY1pyOHNhUHBHMC9XbFBPREEKZE5v' + + 'V1MxWVFidkxnQkR5SVBpR2doejJRV2lFcjY3em53ZkNVdXpqNiszVUtFKzFXQkNyYVRjemZrdHVj' + + 'OTZyLwphcDRPNDJFZWFnU1dNT0ZoZ1AyYWQ4R1JmRGovcEl4N0NlY3pkVUFkVThnc1A1R0lYR3M0' + + 'QU40eUEwL0Y0dUxHCloxbklRT3ZKS2syZnFvWjZNdHd2dEswQ2dZRUFnSjdGTGVDRTkzUmYyZGdD' + + 'ZFRHWGJZZlpKc3M1bEFLNkV0NUwKNmJ1ZFN5dWw1Z0VPWkgyekNsQlJjZFJSMUFNbSt1V1ZoSW8x' + + 'cERLckFlQ2g1MnIvemRmakxLQXNIejkrQWQ3aQpHUEdzVmw0Vm5jaDFTMzQ0bHJKUGUzQklLZ2dj' + + 'L1hncDNTYnNzcHJMY2orT0wyZElrOUpXbzZ1Y3hmMUJmMkwwCjJlbGhBUWtDZ1lCWHN5elZWL1pK' + + 'cVhOcFdDZzU1TDNVRm9UTHlLU3FsVktNM1dpRzVCS240QWF6VkNITCtHUVUKeHd4U2dSOWZRNElu' + + 'dStyUHJOM0lteWswbEtQR0Y5U3pDUlJUaUpGUjcyc05xbE82bDBWOENXUkFQVFBKY2dxVgoxVThO' + + 'SEs4YjNaaUlvR0orbXNOenBkeHJqNjJIM0E2K1krQXNOWTRTbVVUWEg5eWpnK251a2c9PQotLS0t' + + 'LUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo=', + pub: '' + + 'LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FR' + + 'OEFNSUlCQ2dLQ0FRRUF4VEp1SzJhR0xuMWRYSktEaDRNdwpQTFVrbDNISTVwR25HNWFjNGwvMGlo' + + 'bXE4Y3dDK0ZWUGdaTVM1OWF5a2lzQit6Qzd2dHZrSmsvYnYrQlNPWDdvCnhkSXN1TDNkS1FGcHVY' + + 'WFZmcmRiOTV3WW40TSsvbmpFaFhNbGhWTUgvT0NpQWc5SktoVEtXTDZHUldaQUFoQTcKbEJSaGdT' + + 'TkRUaVRDNTFDYmlLN3hBNnBONCt0UUh4b21KUFhyWlJrYkIya2xPZld3YnY5M1kzSjFLRkQraTBQ' + + 'TQpRSEx3N3JoRXVteEM5MytISFVWWVZIN0gxVFBaSDFiZFVKSjAyZ1FleWxKc3NZQ0p5ZFpQek5U' + + 'L3p1dHMvS0pXCmRSdjVseHdHOXU5dE1OTWdoSmJtQWFNa01HaStvN1BORXlQM3FIRnJZcFloczVw' + + 'cUxITVJOQjc4UU05SWVOakwKRndJREFRQUIKLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tCg==', + der: '' + + 'MIIDBjCCAe4CCQDI2qWdA3/VpDANBgkqhkiG9w0BAQUFADBFMQswCQYDVQQGEwJBVTETMBEGA1UE' + + 'CAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMB4XDTE0MDcx' + + 'NjAxMzM1MVoXDTE1MDcxNjAxMzM1MVowRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3Rh' + + 'dGUxITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDCCASIwDQYJKoZIhvcNAQEBBQAD' + + 'ggEPADCCAQoCggEBAMUybitmhi59XVySg4eDMDy1JJdxyOaRpxuWnOJf9IoZqvHMAvhVT4GTEufW' + + 'spIrAfswu77b5CZP27/gUjl+6MXSLLi93SkBabl11X63W/ecGJ+DPv54xIVzJYVTB/zgogIPSSoU' + + 'yli+hkVmQAIQO5QUYYEjQ04kwudQm4iu8QOqTePrUB8aJiT162UZGwdpJTn1sG7/d2NydShQ/otD' + + 'zEBy8O64RLpsQvd/hx1FWFR+x9Uz2R9W3VCSdNoEHspSbLGAicnWT8zU/87rbPyiVnUb+ZccBvbv' + + 'bTDTIISW5gGjJDBovqOzzRMj96hxa2KWIbOaaixzETQe/EDPSHjYyxcCAwEAATANBgkqhkiG9w0B' + + 'AQUFAAOCAQEAL6AMMfC3TlRcmsIgHxjVD4XYtISlldnrn2X9zvFbJKCpNy8XQQosQxrhyfzPHQKj' + + 'lS2L/KCGMnjx9QkYD2Hlp1MJ1uVv9888th/gcZOv3Or3hQyi5K1Sh5xCG+69lUOqUEGu9B4irsqo' + + 'FomQVbQolSy+t4apdJi7kuEDwFDk4gZiVEfsuX+naN5a6pCnWnhX1Vf4fKwfkLobKKXm2zQVsjxl' + + 'wBAqOEmJGDLoRMXH56qJnEZ/dqsczaJOHQSi9mFEHL0r5rsEDTT5AVxdnBfNnyGaCH7/zANEko+F' + + 'GBj1JdJaJgFTXdbxDoyoPTPD+LJqSK5XYToo46y/T0u9CLveNA==', + pem: '' + + 'LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURCakNDQWU0Q0NRREkycVdkQTMvVnBEQU5C' + + 'Z2txaGtpRzl3MEJBUVVGQURCRk1Rc3dDUVlEVlFRR0V3SkIKVlRFVE1CRUdBMVVFQ0F3S1UyOXRa' + + 'UzFUZEdGMFpURWhNQjhHQTFVRUNnd1lTVzUwWlhKdVpYUWdWMmxrWjJsMApjeUJRZEhrZ1RIUmtN' + + 'QjRYRFRFME1EY3hOakF4TXpNMU1Wb1hEVEUxTURjeE5qQXhNek0xTVZvd1JURUxNQWtHCkExVUVC' + + 'aE1DUVZVeEV6QVJCZ05WQkFnTUNsTnZiV1V0VTNSaGRHVXhJVEFmQmdOVkJBb01HRWx1ZEdWeWJt' + + 'VjAKSUZkcFpHZHBkSE1nVUhSNUlFeDBaRENDQVNJd0RRWUpLb1pJaHZjTkFRRUJCUUFEZ2dFUEFE' + + 'Q0NBUW9DZ2dFQgpBTVV5Yml0bWhpNTlYVnlTZzRlRE1EeTFKSmR4eU9hUnB4dVduT0pmOUlvWnF2' + + 'SE1BdmhWVDRHVEV1ZldzcElyCkFmc3d1NzdiNUNaUDI3L2dVamwrNk1YU0xMaTkzU2tCYWJsMTFY' + + 'NjNXL2VjR0orRFB2NTR4SVZ6SllWVEIvemcKb2dJUFNTb1V5bGkraGtWbVFBSVFPNVFVWVlFalEw' + + 'NGt3dWRRbTRpdThRT3FUZVByVUI4YUppVDE2MlVaR3dkcApKVG4xc0c3L2QyTnlkU2hRL290RHpF' + + 'Qnk4TzY0Ukxwc1F2ZC9oeDFGV0ZSK3g5VXoyUjlXM1ZDU2ROb0VIc3BTCmJMR0FpY25XVDh6VS84' + + 'N3JiUHlpVm5VYitaY2NCdmJ2YlREVElJU1c1Z0dqSkRCb3ZxT3p6Uk1qOTZoeGEyS1cKSWJPYWFp' + + 'eHpFVFFlL0VEUFNIall5eGNDQXdFQUFUQU5CZ2txaGtpRzl3MEJBUVVGQUFPQ0FRRUFMNkFNTWZD' + + 'MwpUbFJjbXNJZ0h4alZENFhZdElTbGxkbnJuMlg5enZGYkpLQ3BOeThYUVFvc1F4cmh5ZnpQSFFL' + + 'amxTMkwvS0NHCk1uang5UWtZRDJIbHAxTUoxdVZ2OTg4OHRoL2djWk92M09yM2hReWk1SzFTaDV4' + + 'Q0crNjlsVU9xVUVHdTlCNGkKcnNxb0ZvbVFWYlFvbFN5K3Q0YXBkSmk3a3VFRHdGRGs0Z1ppVkVm' + + 'c3VYK25hTjVhNnBDblduaFgxVmY0Zkt3ZgprTG9iS0tYbTJ6UVZzanhsd0JBcU9FbUpHRExvUk1Y' + + 'SDU2cUpuRVovZHFzY3phSk9IUVNpOW1GRUhMMHI1cnNFCkRUVDVBVnhkbkJmTm55R2FDSDcvekFO' + + 'RWtvK0ZHQmoxSmRKYUpnRlRYZGJ4RG95b1BUUEQrTEpxU0s1WFlUb28KNDZ5L1QwdTlDTHZlTkE9' + + 'PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==' +}; + +x509.priv = new Buffer(x509.priv, 'base64'); +x509.pub = new Buffer(x509.pub, 'base64'); +x509.der = new Buffer(x509.der, 'base64'); +x509.pem = new Buffer(x509.pem, 'base64'); + +function startServer(cb) { + if (G.$http.__server) { + setTimeout(function() { + return cb(null, G.$http.__server); + }, 1); + return; + } + + var server = { + POST: { + + /** + * Receive "I want to pay" + */ + + '/-/request': function(req, cb) { + var res = { + statusCode: 200, + headers: {}, + body: {} + }; + + var uid = 0; + + console.log('Received payment "request" from %s.', req.socket.remoteAddress); + + var outputs = []; + + [2000, 1000].forEach(function(value) { + var po = new PayPro(); + po = po.makeOutput(); + // number of satoshis to be paid + po.set('amount', value); + // a TxOut script where the payment should be sent. similar to OP_CHECKSIG + po.set('script', new Buffer([ + 118, // OP_DUP + 169, // OP_HASH160 + 76, // OP_PUSHDATA1 + 20, // number of bytes + 0xcf, + 0xbe, + 0x41, + 0xf4, + 0xa5, + 0x18, + 0xed, + 0xc2, + 0x5a, + 0xf7, + 0x1b, + 0xaf, + 0xc7, + 0x2f, + 0xb6, + 0x1b, + 0xfc, + 0xfc, + 0x4f, + 0xcd, + 136, // OP_EQUALVERIFY + 172 // OP_CHECKSIG + ])); + outputs.push(po.message); + }); + + /** + * Payment Details + */ + + var mdata = new Buffer([0]); + uid++; + if (uid > 0xffff) { + throw new Error('UIDs bigger than 0xffff not supported.'); + } else if (uid > 0xff) { + mdata = new Buffer([(uid >> 8) & 0xff, (uid >> 0) & 0xff]) + } else { + mdata = new Buffer([0, uid]) + } + var now = Date.now() / 1000 | 0; + var pd = new PayPro(); + pd = pd.makePaymentDetails(); + pd.set('network', 'test'); + pd.set('outputs', outputs); + pd.set('time', now); + pd.set('expires', now * 60 * 60 * 24); + pd.set('memo', 'Hello, this is the server, we would like some money.'); + var port = +req.headers.host.split(':')[1] || server.port; + pd.set('payment_url', 'https://localhost:' + port + '/-/pay'); + pd.set('merchant_data', mdata); + + /* + * PaymentRequest + */ + + var cr = new PayPro(); + cr = cr.makeX509Certificates(); + cr.set('certificate', [x509.der]); + + // We send the PaymentRequest to the customer + var pr = new PayPro(); + pr = pr.makePaymentRequest(); + pr.set('payment_details_version', 1); + pr.set('pki_type', 'x509+sha256'); + pr.set('pki_data', cr.serialize()); + pr.set('serialized_payment_details', pd.serialize()); + pr.sign(x509.priv); + + pr = pr.serialize(); + + // BIP-71 - set the content-type + res.headers['Content-Type'] = PayPro.PAYMENT_REQUEST_CONTENT_TYPE; + res.headers['Content-Length'] = pr.length + ''; + res.headers['Content-Transfer-Encoding'] = 'binary'; + + res.body = pr; + + return cb(null, res, res.body); + }, + + /** + * Receive Payment + */ + + '/-/pay': function(req, cb) { + var body = req.body; + + var res = { + statusCode: 200, + headers: {}, + body: {} + }; + + body = PayPro.Payment.decode(body); + + var pay = new PayPro(); + pay = pay.makePayment(body); + var merchant_data = pay.get('merchant_data'); + var transactions = pay.get('transactions'); + var refund_to = pay.get('refund_to'); + var memo = pay.get('memo'); + + console.log('Received payment from %s.', req.socket.remoteAddress); + console.log('Customer Message: %s', memo); + console.log('Payment Message:'); + console.log(pay); + + // We send this to the customer after receiving a Payment + // Then we propogate the transaction through bitcoin network + var ack = new PayPro(); + ack = ack.makePaymentACK(); + ack.set('payment', pay.message); + ack.set('memo', 'Thank you for your payment!'); + + ack = ack.serialize(); + + // BIP-71 - set the content-type + res.headers['Content-Type'] = PayPro.PAYMENT_ACK_CONTENT_TYPE; + res.headers['Content-Length'] = ack.length + ''; + res.headers['Content-Transfer-Encoding'] = 'binary'; + + transactions = transactions.map(function(tx) { + tx.buffer = tx.buffer.slice(tx.offset, tx.limit); + var ptx = new bitcore.Transaction(); + ptx.parse(tx.buffer); + return ptx; + }); + + transactions.forEach(function(tx) { + var id = tx.getHash().toString('hex'); + console.log(''); + console.log('Sending transaction with txid: %s', id); + console.log(tx.getStandardizedObject()); + }); + + res.body = ack; + + return cb(null, res, res.body); + } + }, + listen: function() { + }, + close: function() { + } + }; + + G.$http = function(options) { + var ret = { + success: function(cb) { + this._success = cb; + return this; + }, + error: function(cb) { + this._error = cb; + return this; + }, + _success: function() { + ; + }, + _error: function(_, err) { + throw err; + } + }; + var method = (options.method || 'GET').toUpperCase(); + var uri = options.uri || options.url; + var path = uri.replace(/^https?:\/\/[^\/]+/, ''); + var req = options; + req.headers = req.headers || {}; + req.body = req.body || {}; + req.socket = { + remoteAddress: 'localhost' + }; + req.headers['Host'] = 'localhost:8080'; + Object.keys(req.headers).forEach(function(key) { + req.headers[key] = req.headers[key] + ''; + req.headers[key.toLowerCase()] = req.headers[key] + ''; + }); + setTimeout(function() { + server[method][path](req, function(err, res, body) { + if (err) return ret._error(null, err, null, options); + Object.keys(res.headers).forEach(function(key) { + res.headers[key] = res.headers[key] + ''; + res.headers[key.toLowerCase()] = res.headers[key] + ''; + }); + return ret._success(body, res.statusCode, res.headers, options); + }); + }, 1); + return ret; + }; + + G.$http.__server = server; + + setTimeout(function() { + return cb(null, server); + }, 1); +} + +module.exports = startServer; diff --git a/test/test.PayPro.js b/test/test.PayPro.js index 3840a8f54..8f4244b5a 100644 --- a/test/test.PayPro.js +++ b/test/test.PayPro.js @@ -24,317 +24,6 @@ var PayPro = bitcore.PayPro; var G = is_browser ? window : global; G.SSL_UNTRUSTED = true; -var x509 = { - priv: '' - + 'LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcEFJQkFBS0NBUUVBeFRKdUsyYUdM' - + 'bjFkWEpLRGg0TXdQTFVrbDNISTVwR25HNWFjNGwvMGlobXE4Y3dDCitGVlBnWk1TNTlheWtpc0Ir' - + 'ekM3dnR2a0prL2J2K0JTT1g3b3hkSXN1TDNkS1FGcHVYWFZmcmRiOTV3WW40TSsKL25qRWhYTWxo' - + 'Vk1IL09DaUFnOUpLaFRLV0w2R1JXWkFBaEE3bEJSaGdTTkRUaVRDNTFDYmlLN3hBNnBONCt0UQpI' - + 'eG9tSlBYclpSa2JCMmtsT2ZXd2J2OTNZM0oxS0ZEK2kwUE1RSEx3N3JoRXVteEM5MytISFVWWVZI' - + 'N0gxVFBaCkgxYmRVSkowMmdRZXlsSnNzWUNKeWRaUHpOVC96dXRzL0tKV2RSdjVseHdHOXU5dE1O' - + 'TWdoSmJtQWFNa01HaSsKbzdQTkV5UDNxSEZyWXBZaHM1cHFMSE1STkI3OFFNOUllTmpMRndJREFR' - + 'QUJBb0lCQVFERVJyalBiQUdjbmwxaAorZGIrOTczNGZ0aElBUkpWSko1dTRFK1JKcThSRWhGTEVL' - + 'UFlKNW0yUC94dVZBMXpYV2xnYXhaRUZ6d1VRaUpZCjdsOEpLVjlwSHhReVlaQ1M4dndYZzhpWGtz' - + 'dndQaWRvQmN1YW4vd0RWQ1FCZXk2VkxjVXpSYUd1Ui9sTHNYK1YKN2Z0QjBvUnFsSXFrYmNQZE1N' - + 'dnFUeG93UnVoUG11Q3JWVGpPNHBiTnFuU09OUExPaUovRkFYYjJwZnpGZnBCUgpHeCtFTW16d2Ur' - + 'SEZuSkJHRGhIWjk5bm4vVEJmYUp6TlZDcURZLzNid3o1WDdIUU5ZN1QrSnlUVUZzZVE5NHhzCnpy' - + 'a2lidGRmVGNUanB1K1VoWm80c1p6Q3IrZkhHWm9FOUdEUHF0ZDRnQ3ByazRFS0pzbXFCRVN4QlhT' - + 'RGhZZ04KOXBVRDM4c1pBb0dCQU9yZkRqdDZaL0ZDamFuVThXek5GaWYrOVQxQTJ4b013RDVWU2xN' - + 'dVJyWW1HbGZyMEM5TQpmMUVvZ2l2dVRrYnA3cmtnZFRhWVRTYndmTnFaQkt4Y3R5YzdCaGRwWnhE' - + 'RVdKa2Z5cThxVngvem1Cek1JK1ZzCjJLYi9hcHZXcmJlb3NET0NyeUg1YzhKc1VUOXhUWDNYYnhF' - + 'anlPSlFCU1lHRE1qUHlKNkU5czZMQW9HQkFOYnYKd2d0S2Nra0tLbDJhNXZzaGR2RENnNnFLL1Fn' - + 'T20vNktUSlVKRVNqaHoydFIrZlBWUjcwVEg5UmhoVFJscERXQgpCd3oyU2NCc1RRNDIvTGsxRnky' - + 'MFQvck12S3VmSEw1VE1BNGZ6NWRxMUxIbmN6ejZVazVnWEtBT09rUjlVdVhpClR0eTNoREcyQkM4' - + 'Nk1LTVJ4SjUxRWJxam94d0VSMTAwU2FuTVBmTWxBb0dBSUhLY1pyOHNhUHBHMC9XbFBPREEKZE5v' - + 'V1MxWVFidkxnQkR5SVBpR2doejJRV2lFcjY3em53ZkNVdXpqNiszVUtFKzFXQkNyYVRjemZrdHVj' - + 'OTZyLwphcDRPNDJFZWFnU1dNT0ZoZ1AyYWQ4R1JmRGovcEl4N0NlY3pkVUFkVThnc1A1R0lYR3M0' - + 'QU40eUEwL0Y0dUxHCloxbklRT3ZKS2syZnFvWjZNdHd2dEswQ2dZRUFnSjdGTGVDRTkzUmYyZGdD' - + 'ZFRHWGJZZlpKc3M1bEFLNkV0NUwKNmJ1ZFN5dWw1Z0VPWkgyekNsQlJjZFJSMUFNbSt1V1ZoSW8x' - + 'cERLckFlQ2g1MnIvemRmakxLQXNIejkrQWQ3aQpHUEdzVmw0Vm5jaDFTMzQ0bHJKUGUzQklLZ2dj' - + 'L1hncDNTYnNzcHJMY2orT0wyZElrOUpXbzZ1Y3hmMUJmMkwwCjJlbGhBUWtDZ1lCWHN5elZWL1pK' - + 'cVhOcFdDZzU1TDNVRm9UTHlLU3FsVktNM1dpRzVCS240QWF6VkNITCtHUVUKeHd4U2dSOWZRNElu' - + 'dStyUHJOM0lteWswbEtQR0Y5U3pDUlJUaUpGUjcyc05xbE82bDBWOENXUkFQVFBKY2dxVgoxVThO' - + 'SEs4YjNaaUlvR0orbXNOenBkeHJqNjJIM0E2K1krQXNOWTRTbVVUWEg5eWpnK251a2c9PQotLS0t' - + 'LUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo=', - pub: '' - + 'LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FR' - + 'OEFNSUlCQ2dLQ0FRRUF4VEp1SzJhR0xuMWRYSktEaDRNdwpQTFVrbDNISTVwR25HNWFjNGwvMGlo' - + 'bXE4Y3dDK0ZWUGdaTVM1OWF5a2lzQit6Qzd2dHZrSmsvYnYrQlNPWDdvCnhkSXN1TDNkS1FGcHVY' - + 'WFZmcmRiOTV3WW40TSsvbmpFaFhNbGhWTUgvT0NpQWc5SktoVEtXTDZHUldaQUFoQTcKbEJSaGdT' - + 'TkRUaVRDNTFDYmlLN3hBNnBONCt0UUh4b21KUFhyWlJrYkIya2xPZld3YnY5M1kzSjFLRkQraTBQ' - + 'TQpRSEx3N3JoRXVteEM5MytISFVWWVZIN0gxVFBaSDFiZFVKSjAyZ1FleWxKc3NZQ0p5ZFpQek5U' - + 'L3p1dHMvS0pXCmRSdjVseHdHOXU5dE1OTWdoSmJtQWFNa01HaStvN1BORXlQM3FIRnJZcFloczVw' - + 'cUxITVJOQjc4UU05SWVOakwKRndJREFRQUIKLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tCg==', - der: '' - + 'MIIDBjCCAe4CCQDI2qWdA3/VpDANBgkqhkiG9w0BAQUFADBFMQswCQYDVQQGEwJBVTETMBEGA1UE' - + 'CAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMB4XDTE0MDcx' - + 'NjAxMzM1MVoXDTE1MDcxNjAxMzM1MVowRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3Rh' - + 'dGUxITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDCCASIwDQYJKoZIhvcNAQEBBQAD' - + 'ggEPADCCAQoCggEBAMUybitmhi59XVySg4eDMDy1JJdxyOaRpxuWnOJf9IoZqvHMAvhVT4GTEufW' - + 'spIrAfswu77b5CZP27/gUjl+6MXSLLi93SkBabl11X63W/ecGJ+DPv54xIVzJYVTB/zgogIPSSoU' - + 'yli+hkVmQAIQO5QUYYEjQ04kwudQm4iu8QOqTePrUB8aJiT162UZGwdpJTn1sG7/d2NydShQ/otD' - + 'zEBy8O64RLpsQvd/hx1FWFR+x9Uz2R9W3VCSdNoEHspSbLGAicnWT8zU/87rbPyiVnUb+ZccBvbv' - + 'bTDTIISW5gGjJDBovqOzzRMj96hxa2KWIbOaaixzETQe/EDPSHjYyxcCAwEAATANBgkqhkiG9w0B' - + 'AQUFAAOCAQEAL6AMMfC3TlRcmsIgHxjVD4XYtISlldnrn2X9zvFbJKCpNy8XQQosQxrhyfzPHQKj' - + 'lS2L/KCGMnjx9QkYD2Hlp1MJ1uVv9888th/gcZOv3Or3hQyi5K1Sh5xCG+69lUOqUEGu9B4irsqo' - + 'FomQVbQolSy+t4apdJi7kuEDwFDk4gZiVEfsuX+naN5a6pCnWnhX1Vf4fKwfkLobKKXm2zQVsjxl' - + 'wBAqOEmJGDLoRMXH56qJnEZ/dqsczaJOHQSi9mFEHL0r5rsEDTT5AVxdnBfNnyGaCH7/zANEko+F' - + 'GBj1JdJaJgFTXdbxDoyoPTPD+LJqSK5XYToo46y/T0u9CLveNA==', - pem: '' - + 'LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURCakNDQWU0Q0NRREkycVdkQTMvVnBEQU5C' - + 'Z2txaGtpRzl3MEJBUVVGQURCRk1Rc3dDUVlEVlFRR0V3SkIKVlRFVE1CRUdBMVVFQ0F3S1UyOXRa' - + 'UzFUZEdGMFpURWhNQjhHQTFVRUNnd1lTVzUwWlhKdVpYUWdWMmxrWjJsMApjeUJRZEhrZ1RIUmtN' - + 'QjRYRFRFME1EY3hOakF4TXpNMU1Wb1hEVEUxTURjeE5qQXhNek0xTVZvd1JURUxNQWtHCkExVUVC' - + 'aE1DUVZVeEV6QVJCZ05WQkFnTUNsTnZiV1V0VTNSaGRHVXhJVEFmQmdOVkJBb01HRWx1ZEdWeWJt' - + 'VjAKSUZkcFpHZHBkSE1nVUhSNUlFeDBaRENDQVNJd0RRWUpLb1pJaHZjTkFRRUJCUUFEZ2dFUEFE' - + 'Q0NBUW9DZ2dFQgpBTVV5Yml0bWhpNTlYVnlTZzRlRE1EeTFKSmR4eU9hUnB4dVduT0pmOUlvWnF2' - + 'SE1BdmhWVDRHVEV1ZldzcElyCkFmc3d1NzdiNUNaUDI3L2dVamwrNk1YU0xMaTkzU2tCYWJsMTFY' - + 'NjNXL2VjR0orRFB2NTR4SVZ6SllWVEIvemcKb2dJUFNTb1V5bGkraGtWbVFBSVFPNVFVWVlFalEw' - + 'NGt3dWRRbTRpdThRT3FUZVByVUI4YUppVDE2MlVaR3dkcApKVG4xc0c3L2QyTnlkU2hRL290RHpF' - + 'Qnk4TzY0Ukxwc1F2ZC9oeDFGV0ZSK3g5VXoyUjlXM1ZDU2ROb0VIc3BTCmJMR0FpY25XVDh6VS84' - + 'N3JiUHlpVm5VYitaY2NCdmJ2YlREVElJU1c1Z0dqSkRCb3ZxT3p6Uk1qOTZoeGEyS1cKSWJPYWFp' - + 'eHpFVFFlL0VEUFNIall5eGNDQXdFQUFUQU5CZ2txaGtpRzl3MEJBUVVGQUFPQ0FRRUFMNkFNTWZD' - + 'MwpUbFJjbXNJZ0h4alZENFhZdElTbGxkbnJuMlg5enZGYkpLQ3BOeThYUVFvc1F4cmh5ZnpQSFFL' - + 'amxTMkwvS0NHCk1uang5UWtZRDJIbHAxTUoxdVZ2OTg4OHRoL2djWk92M09yM2hReWk1SzFTaDV4' - + 'Q0crNjlsVU9xVUVHdTlCNGkKcnNxb0ZvbVFWYlFvbFN5K3Q0YXBkSmk3a3VFRHdGRGs0Z1ppVkVm' - + 'c3VYK25hTjVhNnBDblduaFgxVmY0Zkt3ZgprTG9iS0tYbTJ6UVZzanhsd0JBcU9FbUpHRExvUk1Y' - + 'SDU2cUpuRVovZHFzY3phSk9IUVNpOW1GRUhMMHI1cnNFCkRUVDVBVnhkbkJmTm55R2FDSDcvekFO' - + 'RWtvK0ZHQmoxSmRKYUpnRlRYZGJ4RG95b1BUUEQrTEpxU0s1WFlUb28KNDZ5L1QwdTlDTHZlTkE9' - + 'PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==' -}; - -x509.priv = new Buffer(x509.priv, 'base64'); -x509.pub = new Buffer(x509.pub, 'base64'); -x509.der = new Buffer(x509.der, 'base64'); -x509.pem = new Buffer(x509.pem, 'base64'); - -function startServer(cb) { - var server = { - POST: { - - /** - * Receive "I want to pay" - */ - - '/-/request': function(req, cb) { - var res = { - statusCode: 200, - headers: {}, - body: {} - }; - - var uid = 0; - - console.log('Received payment "request" from %s.', req.socket.remoteAddress); - - var outputs = []; - - [2000, 1000].forEach(function(value) { - var po = new PayPro(); - po = po.makeOutput(); - // number of satoshis to be paid - po.set('amount', value); - // a TxOut script where the payment should be sent. similar to OP_CHECKSIG - po.set('script', new Buffer([ - 118, // OP_DUP - 169, // OP_HASH160 - 76, // OP_PUSHDATA1 - 20, // number of bytes - 0xcf, - 0xbe, - 0x41, - 0xf4, - 0xa5, - 0x18, - 0xed, - 0xc2, - 0x5a, - 0xf7, - 0x1b, - 0xaf, - 0xc7, - 0x2f, - 0xb6, - 0x1b, - 0xfc, - 0xfc, - 0x4f, - 0xcd, - 136, // OP_EQUALVERIFY - 172 // OP_CHECKSIG - ])); - outputs.push(po.message); - }); - - /** - * Payment Details - */ - - var mdata = new Buffer([0]); - uid++; - if (uid > 0xffff) { - throw new Error('UIDs bigger than 0xffff not supported.'); - } else if (uid > 0xff) { - mdata = new Buffer([(uid >> 8) & 0xff, (uid >> 0) & 0xff]) - } else { - mdata = new Buffer([0, uid]) - } - var now = Date.now() / 1000 | 0; - var pd = new PayPro(); - pd = pd.makePaymentDetails(); - pd.set('network', 'test'); - pd.set('outputs', outputs); - pd.set('time', now); - pd.set('expires', now * 60 * 60 * 24); - pd.set('memo', 'Hello, this is the server, we would like some money.'); - var port = +req.headers.host.split(':')[1] || server.port; - pd.set('payment_url', 'https://localhost:' + port + '/-/pay'); - pd.set('merchant_data', mdata); - - /* - * PaymentRequest - */ - - var cr = new PayPro(); - cr = cr.makeX509Certificates(); - cr.set('certificate', [x509.der]); - - // We send the PaymentRequest to the customer - var pr = new PayPro(); - pr = pr.makePaymentRequest(); - pr.set('payment_details_version', 1); - pr.set('pki_type', 'x509+sha256'); - pr.set('pki_data', cr.serialize()); - pr.set('serialized_payment_details', pd.serialize()); - pr.sign(x509.priv); - - pr = pr.serialize(); - - // BIP-71 - set the content-type - res.headers['Content-Type'] = PayPro.PAYMENT_REQUEST_CONTENT_TYPE; - res.headers['Content-Length'] = pr.length + ''; - res.headers['Content-Transfer-Encoding'] = 'binary'; - - res.body = pr; - - return cb(null, res, res.body); - }, - - /** - * Receive Payment - */ - - '/-/pay': function(req, cb) { - var body = req.body; - - var res = { - statusCode: 200, - headers: {}, - body: {} - }; - - body = PayPro.Payment.decode(body); - - var pay = new PayPro(); - pay = pay.makePayment(body); - var merchant_data = pay.get('merchant_data'); - var transactions = pay.get('transactions'); - var refund_to = pay.get('refund_to'); - var memo = pay.get('memo'); - - console.log('Received payment from %s.', req.socket.remoteAddress); - console.log('Customer Message: %s', memo); - console.log('Payment Message:'); - console.log(pay); - - // We send this to the customer after receiving a Payment - // Then we propogate the transaction through bitcoin network - var ack = new PayPro(); - ack = ack.makePaymentACK(); - ack.set('payment', pay.message); - ack.set('memo', 'Thank you for your payment!'); - - ack = ack.serialize(); - - // BIP-71 - set the content-type - res.headers['Content-Type'] = PayPro.PAYMENT_ACK_CONTENT_TYPE; - res.headers['Content-Length'] = ack.length + ''; - res.headers['Content-Transfer-Encoding'] = 'binary'; - - transactions = transactions.map(function(tx) { - tx.buffer = tx.buffer.slice(tx.offset, tx.limit); - var ptx = new bitcore.Transaction(); - ptx.parse(tx.buffer); - return ptx; - }); - - transactions.forEach(function(tx) { - var id = tx.getHash().toString('hex'); - console.log(''); - console.log('Sending transaction with txid: %s', id); - console.log(tx.getStandardizedObject()); - }); - - res.body = ack; - - return cb(null, res, res.body); - } - }, - listen: function() { - }, - close: function() { - } - }; - - G.$http = function(options) { - var ret = { - success: function(cb) { - this._success = cb; - return this; - }, - error: function(cb) { - this._error = cb; - return this; - }, - _success: function() { - ; - }, - _error: function(_, err) { - throw err; - } - }; - var method = (options.method || 'GET').toUpperCase(); - var uri = options.uri || options.url; - var path = uri.replace(/^https?:\/\/[^\/]+/, ''); - var req = options; - req.headers = req.headers || {}; - req.body = req.body || {}; - req.socket = { - remoteAddress: 'localhost' - }; - req.headers['Host'] = 'localhost:8080'; - Object.keys(req.headers).forEach(function(key) { - req.headers[key] = req.headers[key] + ''; - req.headers[key.toLowerCase()] = req.headers[key] + ''; - }); - setTimeout(function() { - server[method][path](req, function(err, res, body) { - if (err) return ret._error(null, err, null, options); - Object.keys(res.headers).forEach(function(key) { - res.headers[key] = res.headers[key] + ''; - res.headers[key.toLowerCase()] = res.headers[key] + ''; - }); - return ret._success(body, res.statusCode, res.headers, options); - }); - }, 1); - return ret; - }; - - setTimeout(function() { - return cb(null, server); - }, 1); -} - var server; describe('PayPro (in Wallet) model', function() { From b89ad8f3cc458062f4f5495208e7f54255aa393c Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Tue, 29 Jul 2014 17:28:47 -0700 Subject: [PATCH 021/178] paypro: controller unit tests for payment protocol. --- js/models/core/Wallet.js | 3 ++- test/test.PayPro.js | 1 + test/unit/controllers/controllersSpec.js | 26 ++++++++++++++++++++++++ 3 files changed, 29 insertions(+), 1 deletion(-) diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index 3a6ce5bbe..091f486f9 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -1027,7 +1027,8 @@ Wallet.prototype.createPaymentTxSync = function(options, merchantData, unspent) var outs = []; merchantData.pr.pd.outputs.forEach(function(output) { outs.push({ - address: self.getAddressesStr()[0] || '1NGYre1pSqTnCXaqN5gLQ1e2KNTJXjDhtF', // dummy address + address: self.getAddressesStr()[0] + || 'mfWxJ45yp2SFn7UciZyNpvDKrzbhyfKrY8', // dummy address (testnet 0 * hash160) amountSatStr: '0' // dummy amount }); }); diff --git a/test/test.PayPro.js b/test/test.PayPro.js index 8f4244b5a..cdf337f85 100644 --- a/test/test.PayPro.js +++ b/test/test.PayPro.js @@ -20,6 +20,7 @@ var TransactionBuilder = bitcore.TransactionBuilder; var Transaction = bitcore.Transaction; var Address = bitcore.Address; var PayPro = bitcore.PayPro; +var startServer = require('./mocks/FakePayProServer'); var G = is_browser ? window : global; G.SSL_UNTRUSTED = true; diff --git a/test/unit/controllers/controllersSpec.js b/test/unit/controllers/controllersSpec.js index 818eac500..10c9891d9 100644 --- a/test/unit/controllers/controllersSpec.js +++ b/test/unit/controllers/controllersSpec.js @@ -217,6 +217,32 @@ describe("Unit: Controllers", function() { sinon.assert.callCount(scope.loadTxs, 1); }); + it('should create a payment protocol transaction proposal', function() { + var uri = 'bitcoin:1JqniWpWNA6Yvdivg3y9izLidETnurxRQm?amount=0.00001000&r=https://localhost:8080/-/request'; + sendForm.address.$setViewValue(uri); + sendForm.amount.$setViewValue(1000); + + scope.wallet.totalCopayers = scope.wallet.requiredCopayers = 3; + var spy = sinon.spy(scope.wallet, 'createTx'); + var spy2 = sinon.spy(scope.wallet, 'sendTx'); + scope.submitForm(sendForm); + sinon.assert.callCount(spy, 1); + sinon.assert.callCount(spy2, 0); + }); + + it('should create and send a payment protocol transaction proposal', function() { + var uri = 'bitcoin:1JqniWpWNA6Yvdivg3y9izLidETnurxRQm?amount=0.00001000&r=https://localhost:8080/-/request'; + sendForm.address.$setViewValue(uri); + sendForm.amount.$setViewValue(1000); + + scope.wallet.totalCopayers = scope.wallet.requiredCopayers = 1; + var spy = sinon.spy(scope.wallet, 'createTx'); + var spy2 = sinon.spy(scope.wallet, 'sendTx'); + + scope.submitForm(sendForm); + sinon.assert.callCount(spy, 1); + sinon.assert.callCount(spy2, 1); + }); }); describe("Unit: Version Controller", function() { From 1c4d292a96ccfbc23bfb21ae25a95a2b83200fb9 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Tue, 29 Jul 2014 17:44:10 -0700 Subject: [PATCH 022/178] paypro: fix mock server. unit controller tests. --- test/mocks/FakePayProServer.js | 14 +++++++++++--- test/test.PayPro.js | 10 ++++++---- test/unit/controllers/controllersSpec.js | 18 ++++++++++++++++++ 3 files changed, 35 insertions(+), 7 deletions(-) diff --git a/test/mocks/FakePayProServer.js b/test/mocks/FakePayProServer.js index 5837af4bb..eaf611168 100644 --- a/test/mocks/FakePayProServer.js +++ b/test/mocks/FakePayProServer.js @@ -93,13 +93,18 @@ x509.der = new Buffer(x509.der, 'base64'); x509.pem = new Buffer(x509.pem, 'base64'); function startServer(cb) { - if (G.$http.__server) { + if (G.$http && G.$http.__server) { setTimeout(function() { return cb(null, G.$http.__server); }, 1); return; } + var old; + if (G.$http) { + old = G.$http; + } + var server = { POST: { @@ -271,9 +276,12 @@ function startServer(cb) { return cb(null, res, res.body); } }, - listen: function() { + listen: function(port, cb) { + if (cb) return cb(); }, - close: function() { + close: function(cb) { + if (old) G.$http = old; + return cb(); } }; diff --git a/test/test.PayPro.js b/test/test.PayPro.js index cdf337f85..eb819bff2 100644 --- a/test/test.PayPro.js +++ b/test/test.PayPro.js @@ -168,8 +168,9 @@ describe('PayPro (in Wallet) model', function() { should.exist(ntxid); console.log('Sent TX proposal to other copayers:'); console.log([ntxid, ca]); - server.close(); - done(); + server.close(function() { + done(); + }); } else { console.log('Sending TX to merchant server:'); console.log(ntxid); @@ -177,8 +178,9 @@ describe('PayPro (in Wallet) model', function() { should.exist(txid); console.log('TX sent:'); console.log([ntxid, ca]); - server.close(); - done(); + server.close(function() { + done(); + }); }); } }); diff --git a/test/unit/controllers/controllersSpec.js b/test/unit/controllers/controllersSpec.js index 10c9891d9..018c8e171 100644 --- a/test/unit/controllers/controllersSpec.js +++ b/test/unit/controllers/controllersSpec.js @@ -10,6 +10,7 @@ saveAs = function(o) { saveAsLastCall = o; }; +var startServer = require('../../mocks/FakePayProServer'); describe("Unit: Controllers", function() { var invalidForm = { @@ -18,6 +19,8 @@ describe("Unit: Controllers", function() { var scope; + var server; + beforeEach(module('copayApp.services')); beforeEach(module('copayApp.controllers')); @@ -217,6 +220,15 @@ describe("Unit: Controllers", function() { sinon.assert.callCount(scope.loadTxs, 1); }); + it('#start the example server', function(done) { + startServer(function(err, s) { + if (err) return done(err); + server = s; + server.uri = 'https://localhost:8080/-'; + done(); + }); + }); + it('should create a payment protocol transaction proposal', function() { var uri = 'bitcoin:1JqniWpWNA6Yvdivg3y9izLidETnurxRQm?amount=0.00001000&r=https://localhost:8080/-/request'; sendForm.address.$setViewValue(uri); @@ -243,6 +255,12 @@ describe("Unit: Controllers", function() { sinon.assert.callCount(spy, 1); sinon.assert.callCount(spy2, 1); }); + + it('#stop the example server', function(done) { + server.close(function() { + done(); + }); + }); }); describe("Unit: Version Controller", function() { From 350592aa8d492ea70da45ed91030fe386f7d8310 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Tue, 29 Jul 2014 20:12:20 -0700 Subject: [PATCH 023/178] paypro: fix parse error. --- js/controllers/send.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/controllers/send.js b/js/controllers/send.js index 31877c2da..d3d162e10 100644 --- a/js/controllers/send.js +++ b/js/controllers/send.js @@ -66,7 +66,7 @@ angular.module('copayApp.controllers').controller('SendController', $scope.loading = false; var message = 'The transaction proposal has been created'; if (ca) { - message += '.\nThis payment protocol transaction + message += '.\nThis payment protocol transaction' + 'has been verified through ' + ca; } notification.success('Success!', message); From 3f6b5ec6b8e5f49c3dc813b3ebbd20c0132dc2af Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Wed, 30 Jul 2014 09:04:02 -0700 Subject: [PATCH 024/178] paypro: use HDPath instead of Structure. --- js/controllers/send.js | 2 +- js/models/core/Wallet.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/js/controllers/send.js b/js/controllers/send.js index d3d162e10..06ca40abb 100644 --- a/js/controllers/send.js +++ b/js/controllers/send.js @@ -87,7 +87,7 @@ angular.module('copayApp.controllers').controller('SendController', } var uri = address.indexOf('bitcoin:') === 0 - && copay.Structure.parseBitcoinURI(address); + && copay.HDPath.parseBitcoinURI(address); if (uri && uri.merchant) { w.createTx(uri.merchant, commentText, done); } else { diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index 091f486f9..60549782e 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -5,7 +5,7 @@ var http = require('http'); var EventEmitter = imports.EventEmitter || require('events').EventEmitter; var async = require('async'); var preconditions = require('preconditions').singleton(); -var parseBitcoinURI = require('./Structure').parseBitcoinURI; +var parseBitcoinURI = require('./HDPath').parseBitcoinURI; var bitcore = require('bitcore'); var bignum = bitcore.Bignum; From 1a0f782e6da6af51fda7e40d6efdb2d5b3926796 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Wed, 30 Jul 2014 10:59:51 -0700 Subject: [PATCH 025/178] paypro: store total on merchant data specifically for display purposes. --- js/models/core/Wallet.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index 60549782e..599f12f2d 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -880,7 +880,8 @@ Wallet.prototype.receivePaymentRequest = function(options, pr, cb) { merchant_data: merchant_data.toString('hex') }, signature: sig, - ca: ca + ca: ca, + total: 0 } }; @@ -1076,6 +1077,8 @@ Wallet.prototype.createPaymentTxSync = function(options, merchantData, unspent) b.tx.outs[i].v = v; b.tx.outs[i].s = s; + + merchantData.total += v; }); this.log(''); From ac8f5a55bc27fd147fbffa70c35fdbaf5dcf8b0b Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Wed, 30 Jul 2014 11:02:22 -0700 Subject: [PATCH 026/178] paypro: reimplement `merchant` field in uri parser. --- js/models/core/HDPath.js | 1 + 1 file changed, 1 insertion(+) diff --git a/js/models/core/HDPath.js b/js/models/core/HDPath.js index 126b84a0a..71e4ea23f 100644 --- a/js/models/core/HDPath.js +++ b/js/models/core/HDPath.js @@ -67,6 +67,7 @@ HDPath.parseBitcoinURI = function(uri) { }); ret.amount = parseFloat(data.amount); ret.message = data.message; + ret.merchant = data.r; } return ret; From 91414b963ce588ac099dbba650f2ca9f3f5de277 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Wed, 30 Jul 2014 11:03:10 -0700 Subject: [PATCH 027/178] hdpath: fix uri parser to deal with merchant uris. --- js/models/core/HDPath.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/models/core/HDPath.js b/js/models/core/HDPath.js index 71e4ea23f..e589ef71e 100644 --- a/js/models/core/HDPath.js +++ b/js/models/core/HDPath.js @@ -55,7 +55,7 @@ HDPath.parseBitcoinURI = function(uri) { var data = decodeURIComponent(uri); var splitDots = data.split(':'); ret.protocol = splitDots[0]; - data = splitDots[1]; + data = splitDots.slice(1).join(':'); var splitQuestion = data.split('?'); ret.address = splitQuestion[0]; From f9406bbde2f7b4b0b6a02b9a5dafd286ba208f65 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Wed, 30 Jul 2014 13:31:57 -0700 Subject: [PATCH 028/178] paypro: use bignum for totals. --- js/models/core/Wallet.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index 599f12f2d..4f16be2fd 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -881,7 +881,7 @@ Wallet.prototype.receivePaymentRequest = function(options, pr, cb) { }, signature: sig, ca: ca, - total: 0 + total: bignum('0') } }; @@ -924,13 +924,13 @@ Wallet.prototype.sendPaymentTx = function(ntxid, options, cb) { || self.publicKeyRing.getPubKeys(0, false, this.getMyCopayerId())[0]; if (options.refund_to) { - var total = 0; + var total = bignum('0'); for (var i = 0; i < tx.outs.length - 1; i++) { - total += tx.outs[i].v; + total = total.add(bignum.fromBuffer(tx.outs[i].v)); } var rpo = new PayPro(); rpo = rpo.makeOutput(); - rpo.set('amount', total); + rpo.set('amount', +total.toString(10)); rpo.set('script', Buffer.concat([ new Buffer([ @@ -1078,9 +1078,11 @@ Wallet.prototype.createPaymentTxSync = function(options, merchantData, unspent) b.tx.outs[i].v = v; b.tx.outs[i].s = s; - merchantData.total += v; + merchantData.total = merchantData.total.add(bignum.fromBuffer(v)); }); + merchantData.total = merchantData.total.toString(10); + this.log(''); this.log('Created transaction:'); this.log(b.tx.getStandardizedObject()); From 4d244a03f88e9d9f8050009d4ebce8b7794c46e2 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Wed, 30 Jul 2014 13:35:57 -0700 Subject: [PATCH 029/178] paypro: fix total on merchantdata. --- js/models/core/Wallet.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index 4f16be2fd..5bccd710c 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -881,8 +881,8 @@ Wallet.prototype.receivePaymentRequest = function(options, pr, cb) { }, signature: sig, ca: ca, - total: bignum('0') - } + }, + total: bignum('0').toString(10) }; return this.getUnspent(function(err, unspent) { @@ -1050,6 +1050,10 @@ Wallet.prototype.createPaymentTxSync = function(options, merchantData, unspent) var signed = b.sign(keys); } + if (typeof merchantData.total === 'string') { + merchantData.total = bignum(merchantData.total, 10); + } + merchantData.pr.pd.outputs.forEach(function(output, i) { var amount = output.get ? output.get('amount') From ad64ad1e0ef0d7b463ea68f750657d7ea2debf0e Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Wed, 30 Jul 2014 14:33:28 -0700 Subject: [PATCH 030/178] paypro: allow payment protocol addresses in address input. --- js/directives.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/js/directives.js b/js/directives.js index 03681d02b..ec78dffef 100644 --- a/js/directives.js +++ b/js/directives.js @@ -12,6 +12,11 @@ angular.module('copayApp.directives') require: 'ngModel', link: function(scope, elem, attrs, ctrl) { var validator = function(value) { + // Is payment protocol address? + if (value.indexOf('bitcoin:') === 0 && /r=/.test(value)) { + ctrl.$setValidity('validAddress', true); + return value; + } var a = new Address(value); ctrl.$setValidity('validAddress', a.isValid() && a.network().name === config.networkName); return value; From a7f176890fb51694b72d6009db19afbc6901067f Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Wed, 30 Jul 2014 14:40:58 -0700 Subject: [PATCH 031/178] paypro: parse bitcoin uri. show merchant uri properly. --- js/directives.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/js/directives.js b/js/directives.js index ec78dffef..3dea498a4 100644 --- a/js/directives.js +++ b/js/directives.js @@ -13,9 +13,10 @@ angular.module('copayApp.directives') link: function(scope, elem, attrs, ctrl) { var validator = function(value) { // Is payment protocol address? - if (value.indexOf('bitcoin:') === 0 && /r=/.test(value)) { + var uri = copay.HDPath.parseBitcoinURI(value); + if (uri && uri.merchant) { ctrl.$setValidity('validAddress', true); - return value; + return 'Merchant: '+ uri.merchant; } var a = new Address(value); ctrl.$setValidity('validAddress', a.isValid() && a.network().name === config.networkName); From 7c39915dd0228a47b47eedb5af9f5271b622b775 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Wed, 30 Jul 2014 16:53:50 -0700 Subject: [PATCH 032/178] paypro: messy work to get xhr to payment server working. --- js/directives.js | 14 ++++++- js/models/core/HDPath.js | 21 ++++++++++ js/models/core/Wallet.js | 85 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 119 insertions(+), 1 deletion(-) diff --git a/js/directives.js b/js/directives.js index 3dea498a4..7ccb89d5f 100644 --- a/js/directives.js +++ b/js/directives.js @@ -1,8 +1,9 @@ 'use strict'; angular.module('copayApp.directives') + //.directive('validAddress', ['$rootScope', + //function($rootScope) { .directive('validAddress', [ - function() { var bitcore = require('bitcore'); @@ -15,6 +16,17 @@ angular.module('copayApp.directives') // Is payment protocol address? var uri = copay.HDPath.parseBitcoinURI(value); if (uri && uri.merchant) { + scope.wallet.createPaymentTx(uri.merchant, function(ntxid, ca) { + var txp = scope.wallet.txProposals.txps[ntxid]; + if (!txp) return; + var total = txp.merchant.total; + console.log('TOTAL:'); + console.log(total); + var sendForm = angular.element(document).find('[name=sendForm]'); + var amount = angular.element(sendForm).find('#amount') + amount.prop('disabled', true); + amount.val(total); + }); ctrl.$setValidity('validAddress', true); return 'Merchant: '+ uri.merchant; } diff --git a/js/models/core/HDPath.js b/js/models/core/HDPath.js index e589ef71e..919bc8e22 100644 --- a/js/models/core/HDPath.js +++ b/js/models/core/HDPath.js @@ -59,6 +59,7 @@ HDPath.parseBitcoinURI = function(uri) { var splitQuestion = data.split('?'); ret.address = splitQuestion[0]; +/* if (splitQuestion.length > 1) { var search = splitQuestion[1]; data = JSON.parse('{"' + search.replace(/&/g, '","').replace(/=/g, '":"') + '"}', @@ -69,6 +70,26 @@ HDPath.parseBitcoinURI = function(uri) { ret.message = data.message; ret.merchant = data.r; } +*/ + + if (splitQuestion.length > 1) { + var data = {}; + var search = splitQuestion[1]; + var parts = search.split('&'); + var part; + var i = 0; + for (; i < parts.length; i++) { + part = parts[i].split('='); + if (part[0] === '') { + data[part[1]] = part[1]; + } else { + data[part[0]] = decodeURIComponent(part[1]); + } + } + ret.amount = parseFloat(data.amount); + ret.message = data.message; + ret.merchant = data.r; + } return ret; }; diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index 5bccd710c..7939be71d 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -25,6 +25,91 @@ var TxProposals = require('./TxProposals'); var PrivateKey = require('./PrivateKey'); var copayConfig = require('../../../config'); +if (typeof angular !== 'undefined') { + var $http = angular.bootstrap().get('$http'); +} + +var $http = function $http(options, callback) { + if (typeof options === 'string') { + options = { uri: options }; + } + + options.method = options.method || 'GET'; + options.headers = options.headers || {}; + + var ret = { + success: function(cb) { + this._success = cb; + return this; + }, + error: function(cb) { + this._error = cb; + return this; + }, + _success: function() { + ; + }, + _error: function(_, err) { + throw err; + } + }; + + var method = (options.method || 'GET').toUpperCase(); + var uri = options.uri || options.url; + var req = options; + + req.headers = req.headers || {}; + req.body = req.body || {}; + + if (typeof XMLHttpRequest !== 'undefined') { + var xhr = new XMLHttpRequest(); + xhr.open(method, uri, true); + + Object.keys(options.headers).forEach(function(key) { + var val = options.headers[key]; + if (key === 'Content-Length') return; + if (key === 'Content-Transfer-Encoding') return; + xhr.setRequestHeader(key, val); + }); + + // For older browsers: + // xhr.overrideMimeType('text/plain; charset=x-user-defined'); + + // Newer browsers: + xhr.responseType = 'arraybuffer'; + + xhr.onload = function(event) { + var response = xhr.response; + var buf = new Uint8Array(response); + // return callback(null, xhr, buf); + var headers = {}; + (xhr.getAllResponseHeaders() || '').replace( + /(?:\r?\n|^)([^:\r\n]+): *([^\r\n]+)/g, + function($0, $1, $2) { + headers[$1.toLowerCase()] = $2; + } + ); + return ret._success(buf, xhr.status, headers, options); + }; + + xhr.onerror = function(event) { + return ret._error(null, event, null, options); + }; + + if (options.body) { + xhr.send(options.body); + } else { + xhr.send(null); + } + + return ret; + } + + // require('request')(options, callback); + + return ret; +} + function Wallet(opts) { var self = this; From 7915008433e32b974b7b13d427ac223606ded2bd Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Wed, 30 Jul 2014 17:20:50 -0700 Subject: [PATCH 033/178] paypro: remove old parse uri code. more debugging. --- js/directives.js | 9 +++++++-- js/models/core/HDPath.js | 13 ------------- js/models/core/Wallet.js | 17 ++++++++++------- 3 files changed, 17 insertions(+), 22 deletions(-) diff --git a/js/directives.js b/js/directives.js index 7ccb89d5f..2e2e750e3 100644 --- a/js/directives.js +++ b/js/directives.js @@ -15,6 +15,12 @@ angular.module('copayApp.directives') var validator = function(value) { // Is payment protocol address? var uri = copay.HDPath.parseBitcoinURI(value); + var total = '00001000'; + console.log('TOTAL:'); + console.log(total); + var amount = angular.element(document).find('#amount'); + amount.prop('disabled', true); + amount.val(total); if (uri && uri.merchant) { scope.wallet.createPaymentTx(uri.merchant, function(ntxid, ca) { var txp = scope.wallet.txProposals.txps[ntxid]; @@ -22,8 +28,7 @@ angular.module('copayApp.directives') var total = txp.merchant.total; console.log('TOTAL:'); console.log(total); - var sendForm = angular.element(document).find('[name=sendForm]'); - var amount = angular.element(sendForm).find('#amount') + var amount = angular.element(document).find('#amount'); amount.prop('disabled', true); amount.val(total); }); diff --git a/js/models/core/HDPath.js b/js/models/core/HDPath.js index 919bc8e22..12a7c0f73 100644 --- a/js/models/core/HDPath.js +++ b/js/models/core/HDPath.js @@ -59,19 +59,6 @@ HDPath.parseBitcoinURI = function(uri) { var splitQuestion = data.split('?'); ret.address = splitQuestion[0]; -/* - if (splitQuestion.length > 1) { - var search = splitQuestion[1]; - data = JSON.parse('{"' + search.replace(/&/g, '","').replace(/=/g, '":"') + '"}', - function(key, value) { - return key === "" ? value : decodeURIComponent(value); - }); - ret.amount = parseFloat(data.amount); - ret.message = data.message; - ret.merchant = data.r; - } -*/ - if (splitQuestion.length > 1) { var data = {}; var search = splitQuestion[1]; diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index 7939be71d..007c4a51a 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -25,11 +25,17 @@ var TxProposals = require('./TxProposals'); var PrivateKey = require('./PrivateKey'); var copayConfig = require('../../../config'); -if (typeof angular !== 'undefined') { - var $http = angular.bootstrap().get('$http'); +if (typeof window !== 'undefined') { + var G = window; +} else { + var G = global; } -var $http = function $http(options, callback) { +if (typeof angular !== 'undefined') { + var $http = G.$http || angular.bootstrap().get('$http'); +} + +var $http = G.$http || function $http(options, callback) { if (typeof options === 'string') { options = { uri: options }; } @@ -81,7 +87,6 @@ var $http = function $http(options, callback) { xhr.onload = function(event) { var response = xhr.response; var buf = new Uint8Array(response); - // return callback(null, xhr, buf); var headers = {}; (xhr.getAllResponseHeaders() || '').replace( /(?:\r?\n|^)([^:\r\n]+): *([^\r\n]+)/g, @@ -93,7 +98,7 @@ var $http = function $http(options, callback) { }; xhr.onerror = function(event) { - return ret._error(null, event, null, options); + return ret._error(null, new Error(event.message), null, options); }; if (options.body) { @@ -105,8 +110,6 @@ var $http = function $http(options, callback) { return ret; } - // require('request')(options, callback); - return ret; } From 49883779af7ed1ef05a218e68438c2bf7b1e358b Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Wed, 30 Jul 2014 18:15:03 -0700 Subject: [PATCH 034/178] paypro: more messy debugging. --- js/directives.js | 50 +++++++++++++++++++++++++++++++--------- js/models/core/Wallet.js | 2 +- 2 files changed, 40 insertions(+), 12 deletions(-) diff --git a/js/directives.js b/js/directives.js index 2e2e750e3..4a52b2981 100644 --- a/js/directives.js +++ b/js/directives.js @@ -15,26 +15,54 @@ angular.module('copayApp.directives') var validator = function(value) { // Is payment protocol address? var uri = copay.HDPath.parseBitcoinURI(value); - var total = '00001000'; - console.log('TOTAL:'); - console.log(total); - var amount = angular.element(document).find('#amount'); - amount.prop('disabled', true); - amount.val(total); + if (uri && uri.merchant) { + var total = bitcore + .bignum('1000') + .div(config.unitToSatoshi) + .toString(10); + + var amount = angular.element(angular + .element(document) + .find('form') + .find('input')[1]); + amount.val(total); + amount.attr('disabled', true); + + var tamount = angular.element(angular + .element(document) + .find('section') + .find('p')[1]); + tamount.attr('class', tamount.attr('class').replace(' hidden', '')) + tamount.text(total + ' (CA: Internet Widgets Pty Ltd)') + scope.wallet.createPaymentTx(uri.merchant, function(ntxid, ca) { var txp = scope.wallet.txProposals.txps[ntxid]; if (!txp) return; - var total = txp.merchant.total; - console.log('TOTAL:'); - console.log(total); - var amount = angular.element(document).find('#amount'); - amount.prop('disabled', true); + + var total = bitcore + .bignum.fromBuffer(txp.merchant.total) + .div(config.unitToSatoshi) + .toString(10); + + var amount = angular.element(angular + .element(document) + .find('form') + .find('input')[1]); amount.val(total); + amount.attr('disabled', true); + + var tamount = angular.element(angular + .element(document) + .find('section') + .find('p')[1]); + tamount.attr('class', tamount.attr('class').replace(' hidden', '')) + tamount.text(total + ' (CA: ' + ca + ')') }); ctrl.$setValidity('validAddress', true); return 'Merchant: '+ uri.merchant; } + var a = new Address(value); ctrl.$setValidity('validAddress', a.isValid() && a.network().name === config.networkName); return value; diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index 007c4a51a..f1e2cca95 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -111,7 +111,7 @@ var $http = G.$http || function $http(options, callback) { } return ret; -} +}; function Wallet(opts) { var self = this; From 2d129331126d1664c4602ba18dc694f6a1905204 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Wed, 30 Jul 2014 19:14:29 -0700 Subject: [PATCH 035/178] paypro: more debugging. --- app.js | 20 ++++++++++++++++++++ js/models/core/Wallet.js | 6 ++++++ 2 files changed, 26 insertions(+) diff --git a/app.js b/app.js index ebc3b7826..6ed9fba9c 100644 --- a/app.js +++ b/app.js @@ -12,6 +12,26 @@ app.start = function(port, callback) { app.set('port', port); app.use(express.static(__dirname)); + if (process.env.USE_HTTPS) { + process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = '0'; + var fs = require('fs'); + var path = require('path'); + var bc = path.dirname(require.resolve('bitcore/package.json')); + var server = require('https').createServer({ + key: fs.readFileSync(bc + '/test/data/x509.key'), + cert: fs.readFileSync(bc + '/test/data/x509.crt') + }); + var pserver = require(bc + '/examples/PayPro/server.js'); + server.on('request', function(req, res) { + app(req, res); + pserver.app(res, res); + }); + app.listen(port, function() { + callback('https://localhost:' + port); + }); + return; + } + app.listen(port, function() { callback('http://localhost:' + port); }); diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index f1e2cca95..21ac51a15 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -84,6 +84,12 @@ var $http = G.$http || function $http(options, callback) { // Newer browsers: xhr.responseType = 'arraybuffer'; + xhr.onreadystatechange = function() { + if (xhr.readyState == 4) { + ; + } + }; + xhr.onload = function(event) { var response = xhr.response; var buf = new Uint8Array(response); From 295f3a144a728d996bcbeaf574cca8e6cf642e22 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Thu, 31 Jul 2014 11:00:22 -0700 Subject: [PATCH 036/178] paypro: fix server hooking for self-signed paypro testing. --- app.js | 60 ++++++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 56 insertions(+), 4 deletions(-) diff --git a/app.js b/app.js index 6ed9fba9c..4714da0f8 100644 --- a/app.js +++ b/app.js @@ -22,13 +22,65 @@ app.start = function(port, callback) { cert: fs.readFileSync(bc + '/test/data/x509.crt') }); var pserver = require(bc + '/examples/PayPro/server.js'); - server.on('request', function(req, res) { - app(req, res); - pserver.app(res, res); + pserver.removeListener('request', pserver.app); + pserver.on('request', function(req, res) { + var statusCode = res.statusCode; + + var headers = Object.keys(res._headers || {}).reduce(function(out, key) { + out[key] = res._headers[key]; + return out; + }, {}); + + var headerNames = Object.keys(res._headerNames || {}).reduce(function(out, key) { + out[key] = res._headerNames[key]; + return out; + }, {}); + + var writeHead = res.writeHead; + var write = res.write; + var end = res.end; + var status; + + res.writeHead = function(s) { + status = s; + if (status > 400) { + return; + } + return writeHead.apply(this, arguments); + }; + + res.write = function() { + if (status && status > 400) { + return true; + } + return write.apply(this, arguments); + }; + + res.end = function() { + var self = this; + var args = Array.prototype.slice.call(arguments); + process.nextTick(function() { + self.statusCode = statusCode; + self._headers = headers; + self._headerNames = headerNames; + self.writeHead = writeHead; + self.write = write; + self.end = end; + if ((status || self.statusCode) > 400) { + return pserver.app(req, res); + } + return end.apply(self, args); + }); + return true; + }; + + return app(req, res); }); - app.listen(port, function() { + + pserver.listen(port, function() { callback('https://localhost:' + port); }); + return; } From 5105fd4229e60c4cc62addfb5ed4ecaa53950e95 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Thu, 31 Jul 2014 11:00:41 -0700 Subject: [PATCH 037/178] paypro: more server info in validation. --- js/directives.js | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/js/directives.js b/js/directives.js index 4a52b2981..6a78f1338 100644 --- a/js/directives.js +++ b/js/directives.js @@ -33,13 +33,22 @@ angular.module('copayApp.directives') .element(document) .find('section') .find('p')[1]); - tamount.attr('class', tamount.attr('class').replace(' hidden', '')) - tamount.text(total + ' (CA: Internet Widgets Pty Ltd)') + tamount.attr('class', + tamount.attr('class').replace(' hidden', '')); + tamount.text(total + + ' (CA: Internet Widgets Pty Ltd. Expires: ' + + new Date().toISOString() + + '): Hi, we\'d like some bitcoin.'); scope.wallet.createPaymentTx(uri.merchant, function(ntxid, ca) { var txp = scope.wallet.txProposals.txps[ntxid]; if (!txp) return; + var expires = txp.merchant.pr.expires; + var memo = txp.merchant.pr.memo; + var payment_url = txp.merchant.pr.payment_url; + var total = txp.merchant.total; + var total = bitcore .bignum.fromBuffer(txp.merchant.total) .div(config.unitToSatoshi) @@ -56,9 +65,14 @@ angular.module('copayApp.directives') .element(document) .find('section') .find('p')[1]); - tamount.attr('class', tamount.attr('class').replace(' hidden', '')) - tamount.text(total + ' (CA: ' + ca + ')') + tamount.attr('class', + tamount.attr('class').replace(' hidden', '')) + tamount.text(total + ' (CA: ' + ca + + '. Expires: ' + + new Date(expires * 1000).toISOString() + + '): ' + memo); }); + ctrl.$setValidity('validAddress', true); return 'Merchant: '+ uri.merchant; } From 62fe6ce628db07e40d144342c6b519a7c5a21b01 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Thu, 31 Jul 2014 11:15:57 -0700 Subject: [PATCH 038/178] paypro: try to find existing tx proposals. --- js/controllers/send.js | 27 +++++++++++++++++++++++++-- js/models/core/Wallet.js | 1 + 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/js/controllers/send.js b/js/controllers/send.js index 06ca40abb..3a48c33d2 100644 --- a/js/controllers/send.js +++ b/js/controllers/send.js @@ -88,8 +88,31 @@ angular.module('copayApp.controllers').controller('SendController', var uri = address.indexOf('bitcoin:') === 0 && copay.HDPath.parseBitcoinURI(address); - if (uri && uri.merchant) { - w.createTx(uri.merchant, commentText, done); + if (uri.merchant) { + var existing; + + Object.keys(w.txProposals.txps).forEach(function(ntxid, i, obj) { + var txp = w.txProposals.txps[ntxid]; + if (!txp) return; + var total = typeof txp.merchant.total !== 'string' + ? txp.merchant.total.toString(10) + : txp.merchant.total; + if (txp.merchant.request_url === uri.merchant && total === amount) { + existing = txp; + obj.length = 0; + } + }); + + if (existing) { + var tx = existing.builder.build(); + if (!tx.isComplete()) { + $scope.sign(existing.getID()); + } else { + done(existing.getID(), existing.merchant.pr.ca); + } + } else { + w.createTx(uri.merchant, commentText, done); + } } else { w.createTx(address, amount, commentText, done); } diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index 21ac51a15..3d37b9cda 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -976,6 +976,7 @@ Wallet.prototype.receivePaymentRequest = function(options, pr, cb) { signature: sig, ca: ca, }, + request_url: options.uri || options.url, total: bignum('0').toString(10) }; From 035070b78bb4e3da1018f4d40c1585b0157bcb43 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Thu, 31 Jul 2014 11:31:52 -0700 Subject: [PATCH 039/178] paypro: drop existing txp check. --- js/controllers/send.js | 25 +------------------------ 1 file changed, 1 insertion(+), 24 deletions(-) diff --git a/js/controllers/send.js b/js/controllers/send.js index 3a48c33d2..429287947 100644 --- a/js/controllers/send.js +++ b/js/controllers/send.js @@ -89,30 +89,7 @@ angular.module('copayApp.controllers').controller('SendController', var uri = address.indexOf('bitcoin:') === 0 && copay.HDPath.parseBitcoinURI(address); if (uri.merchant) { - var existing; - - Object.keys(w.txProposals.txps).forEach(function(ntxid, i, obj) { - var txp = w.txProposals.txps[ntxid]; - if (!txp) return; - var total = typeof txp.merchant.total !== 'string' - ? txp.merchant.total.toString(10) - : txp.merchant.total; - if (txp.merchant.request_url === uri.merchant && total === amount) { - existing = txp; - obj.length = 0; - } - }); - - if (existing) { - var tx = existing.builder.build(); - if (!tx.isComplete()) { - $scope.sign(existing.getID()); - } else { - done(existing.getID(), existing.merchant.pr.ca); - } - } else { - w.createTx(uri.merchant, commentText, done); - } + w.createTx(uri.merchant, commentText, done); } else { w.createTx(address, amount, commentText, done); } From b7b7b2e38e7c6170c31aae32d7a96cfe1e7facef Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Thu, 31 Jul 2014 11:39:46 -0700 Subject: [PATCH 040/178] paypro: create real paypro txs. start potentially adding more merchant data to notifications. --- js/controllers/send.js | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/js/controllers/send.js b/js/controllers/send.js index 429287947..ee280ee5d 100644 --- a/js/controllers/send.js +++ b/js/controllers/send.js @@ -62,12 +62,14 @@ angular.module('copayApp.controllers').controller('SendController', var w = $rootScope.wallet; function done(ntxid, ca) { + var txp = w.txProposals.txps[ntxid]; + var merchantData = txp.merchant; if (w.isShared()) { $scope.loading = false; var message = 'The transaction proposal has been created'; if (ca) { message += '.\nThis payment protocol transaction' - + 'has been verified through ' + ca; + + 'has been verified through ' + ca + '.'; } notification.success('Success!', message); $scope.loadTxs(); @@ -75,7 +77,9 @@ angular.module('copayApp.controllers').controller('SendController', w.sendTx(ntxid, function(txid, ca) { if (txid) { notification.success('Transaction broadcast', 'Transaction id: ' + txid); - if (ca) notification.success('Root Certificate', ca); + if (ca) { + notification.success('Root Certificate', ca); + } } else { notification.error('Error', 'There was an error sending the transaction.'); } @@ -88,8 +92,12 @@ angular.module('copayApp.controllers').controller('SendController', var uri = address.indexOf('bitcoin:') === 0 && copay.HDPath.parseBitcoinURI(address); - if (uri.merchant) { - w.createTx(uri.merchant, commentText, done); + + if (uri && uri.merchant) { + w.createPaymentTx({ + uri: uri.merchant, + memo: commentText + }, done); } else { w.createTx(address, amount, commentText, done); } From 6b6e2515110e1de3f1b7c1b014ec7fd0474b8e4e Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Thu, 31 Jul 2014 12:08:21 -0700 Subject: [PATCH 041/178] paypro: add fetchPaymentTx, use for form validation. improve css selection. --- js/directives.js | 76 ++++++++++++++++++++-------------------- js/models/core/Wallet.js | 17 +++++++-- 2 files changed, 53 insertions(+), 40 deletions(-) diff --git a/js/directives.js b/js/directives.js index 6a78f1338..a4bf133f9 100644 --- a/js/directives.js +++ b/js/directives.js @@ -17,59 +17,59 @@ angular.module('copayApp.directives') var uri = copay.HDPath.parseBitcoinURI(value); if (uri && uri.merchant) { - var total = bitcore - .bignum('1000') - .div(config.unitToSatoshi) - .toString(10); + // XXX This might be unwise, it might be better to + // create a tentative TX proposal here. + scope.wallet.fetchPaymentTx(uri.merchant, function(err, merchantData) { + // scope.wallet.createPaymentTx(uri.merchant, function(ntxid, ca) { + // var txp = scope.wallet.txProposals.txps[ntxid]; + // if (!txp) return; + var txp = { merchant: merchantData }; - var amount = angular.element(angular - .element(document) - .find('form') - .find('input')[1]); - amount.val(total); - amount.attr('disabled', true); - - var tamount = angular.element(angular - .element(document) - .find('section') - .find('p')[1]); - tamount.attr('class', - tamount.attr('class').replace(' hidden', '')); - tamount.text(total - + ' (CA: Internet Widgets Pty Ltd. Expires: ' - + new Date().toISOString() - + '): Hi, we\'d like some bitcoin.'); - - scope.wallet.createPaymentTx(uri.merchant, function(ntxid, ca) { - var txp = scope.wallet.txProposals.txps[ntxid]; - if (!txp) return; - - var expires = txp.merchant.pr.expires; + var expires = new Date(txp.merchant.pr.expires * 1000); var memo = txp.merchant.pr.memo; var payment_url = txp.merchant.pr.payment_url; var total = txp.merchant.total; - var total = bitcore - .bignum.fromBuffer(txp.merchant.total) + if (typeof total === 'string') { + total = bitcore.bignum(total, 10).toBuffer(); + } + + total = bitcore + .bignum.fromBuffer(total) .div(config.unitToSatoshi) .toString(10); - var amount = angular.element(angular - .element(document) - .find('form') - .find('input')[1]); + // var amount = angular.element(angular + // .element(document) + // .find('form') + // .find('input')[1]); + + var amount = angular.element( + document.querySelector('input#amount')); amount.val(total); amount.attr('disabled', true); - var tamount = angular.element(angular - .element(document) - .find('section') - .find('p')[1]); + // var sendto = angular.element(angular + // .element(document) + // .find('section') + // .find('p')[0]); + + var sendto = angular.element( + document.querySelector('div.send-note > p[ng-class]:first-of-type')); + sendto.html(sendto.html() + '
Server: ' + memo); + + // var tamount = angular.element(angular + // .element(document) + // .find('section') + // .find('p')[1]); + + var tamount = angular.element( + document.querySelector('div.send-note > p[ng-class]:nth-of-type(2)')); tamount.attr('class', tamount.attr('class').replace(' hidden', '')) tamount.text(total + ' (CA: ' + ca + '. Expires: ' - + new Date(expires * 1000).toISOString() + + expires.toISOString() + '): ' + memo); }); diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index 3d37b9cda..73852c584 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -898,6 +898,15 @@ Wallet.prototype.createPaymentTx = function(options, cb) { }); }; +Wallet.prototype.fetchPaymentTx = function(options, cb) { + options = options || {}; + if (typeof options === 'string') { + options = { uri: options }; + } + options.fetch = true; + return this.createPaymentTx(options, cb); +}; + Wallet.prototype.receivePaymentRequest = function(options, pr, cb) { var self = this; @@ -977,9 +986,13 @@ Wallet.prototype.receivePaymentRequest = function(options, pr, cb) { ca: ca, }, request_url: options.uri || options.url, - total: bignum('0').toString(10) + total: bignum('0', 10).toString(10) }; + if (options.fetch) { + return cb(null, merchantData); + } + return this.getUnspent(function(err, unspent) { var ntxid = self.createPaymentTxSync(options, merchantData, unspent); if (ntxid) { @@ -1019,7 +1032,7 @@ Wallet.prototype.sendPaymentTx = function(ntxid, options, cb) { || self.publicKeyRing.getPubKeys(0, false, this.getMyCopayerId())[0]; if (options.refund_to) { - var total = bignum('0'); + var total = bignum('0', 10); for (var i = 0; i < tx.outs.length - 1; i++) { total = total.add(bignum.fromBuffer(tx.outs[i].v)); } From b9d5219426a1131c044a9c7518ff8625698a472f Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Thu, 31 Jul 2014 12:08:46 -0700 Subject: [PATCH 042/178] paypro: remove old code. --- js/directives.js | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/js/directives.js b/js/directives.js index a4bf133f9..0d65a4af5 100644 --- a/js/directives.js +++ b/js/directives.js @@ -20,9 +20,6 @@ angular.module('copayApp.directives') // XXX This might be unwise, it might be better to // create a tentative TX proposal here. scope.wallet.fetchPaymentTx(uri.merchant, function(err, merchantData) { - // scope.wallet.createPaymentTx(uri.merchant, function(ntxid, ca) { - // var txp = scope.wallet.txProposals.txps[ntxid]; - // if (!txp) return; var txp = { merchant: merchantData }; var expires = new Date(txp.merchant.pr.expires * 1000); @@ -39,30 +36,15 @@ angular.module('copayApp.directives') .div(config.unitToSatoshi) .toString(10); - // var amount = angular.element(angular - // .element(document) - // .find('form') - // .find('input')[1]); - var amount = angular.element( document.querySelector('input#amount')); amount.val(total); amount.attr('disabled', true); - // var sendto = angular.element(angular - // .element(document) - // .find('section') - // .find('p')[0]); - var sendto = angular.element( document.querySelector('div.send-note > p[ng-class]:first-of-type')); sendto.html(sendto.html() + '
Server: ' + memo); - // var tamount = angular.element(angular - // .element(document) - // .find('section') - // .find('p')[1]); - var tamount = angular.element( document.querySelector('div.send-note > p[ng-class]:nth-of-type(2)')); tamount.attr('class', From af12b5667841a35bb87ae91a896a50da56c629ff Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Thu, 31 Jul 2014 12:23:06 -0700 Subject: [PATCH 043/178] paypro: maintain received payment requests. --- js/controllers/send.js | 12 ++++++++---- js/models/core/Wallet.js | 33 ++++++++++++++++++++++++++++----- 2 files changed, 36 insertions(+), 9 deletions(-) diff --git a/js/controllers/send.js b/js/controllers/send.js index ee280ee5d..a33081250 100644 --- a/js/controllers/send.js +++ b/js/controllers/send.js @@ -94,10 +94,14 @@ angular.module('copayApp.controllers').controller('SendController', && copay.HDPath.parseBitcoinURI(address); if (uri && uri.merchant) { - w.createPaymentTx({ - uri: uri.merchant, - memo: commentText - }, done); + var data = w.paymentRequests[uri.merchant]; + if (data) { + } else { + w.createPaymentTx({ + uri: uri.merchant, + memo: commentText + }, done); + } } else { w.createTx(address, amount, commentText, done); } diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index 73852c584..1700ec9b1 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -150,6 +150,8 @@ function Wallet(opts) { this.addressBook = opts.addressBook || {}; this.publicKey = this.privateKey.publicHex; + this.paymentRequests = opts.paymentRequests || {}; + //network nonces are 8 byte buffers, representing a big endian number //one nonce for oneself, and then one nonce for each copayer this.network.setHexNonce(opts.networkNonce); @@ -899,12 +901,30 @@ Wallet.prototype.createPaymentTx = function(options, cb) { }; Wallet.prototype.fetchPaymentTx = function(options, cb) { + var self = this; options = options || {}; if (typeof options === 'string') { options = { uri: options }; } options.fetch = true; - return this.createPaymentTx(options, cb); + return this.createPaymentTx(options, function(err, merchantData, options, pr) { + var id = self.hashMerchantData(merchantData); + self.paymentRequests[id] = { + id: id, + merchantData: merchantData, + options: options, + pr: pr + }; + return cb(null, merchantData); + }); +}; + +Wallet.hashMerchantData = +Wallet.prototype.hashMerchantData = function(merchantData) { + return merchantData.request_url + + ':' + merchantData.pr.payment_url + + ':' + merchantData.total + + ':' + merchantData.pr.pd.merchant_data; }; Wallet.prototype.receivePaymentRequest = function(options, pr, cb) { @@ -989,11 +1009,12 @@ Wallet.prototype.receivePaymentRequest = function(options, pr, cb) { total: bignum('0', 10).toString(10) }; - if (options.fetch) { - return cb(null, merchantData); - } - return this.getUnspent(function(err, unspent) { + if (options.fetch) { + self.createPaymentTxSync(options, merchantData, unspent); + return cb(null, merchantData, options, pr); + } + var ntxid = self.createPaymentTxSync(options, merchantData, unspent); if (ntxid) { self.sendIndexes(); @@ -1195,6 +1216,8 @@ Wallet.prototype.createPaymentTxSync = function(options, merchantData, unspent) merchantData.total = merchantData.total.toString(10); + if (options.fetch) return; + this.log(''); this.log('Created transaction:'); this.log(b.tx.getStandardizedObject()); From decd981bce3686945b4b69079fc8abe39e4642a8 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Thu, 31 Jul 2014 12:31:14 -0700 Subject: [PATCH 044/178] paypro: deal with cached payment requests properly. --- js/controllers/send.js | 8 ++++++-- js/directives.js | 2 -- js/models/core/Wallet.js | 21 ++++++++++----------- 3 files changed, 16 insertions(+), 15 deletions(-) diff --git a/js/controllers/send.js b/js/controllers/send.js index a33081250..a9d9995cf 100644 --- a/js/controllers/send.js +++ b/js/controllers/send.js @@ -94,8 +94,12 @@ angular.module('copayApp.controllers').controller('SendController', && copay.HDPath.parseBitcoinURI(address); if (uri && uri.merchant) { - var data = w.paymentRequests[uri.merchant]; - if (data) { + var req = w.paymentRequests[uri.merchant]; + if (req) { + if (commentText) { + req.options.memo = commentText; + } + w.receivePaymentRequest(req.options, req.pr, done); } else { w.createPaymentTx({ uri: uri.merchant, diff --git a/js/directives.js b/js/directives.js index 0d65a4af5..aed56933d 100644 --- a/js/directives.js +++ b/js/directives.js @@ -17,8 +17,6 @@ angular.module('copayApp.directives') var uri = copay.HDPath.parseBitcoinURI(value); if (uri && uri.merchant) { - // XXX This might be unwise, it might be better to - // create a tentative TX proposal here. scope.wallet.fetchPaymentTx(uri.merchant, function(err, merchantData) { var txp = { merchant: merchantData }; diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index 1700ec9b1..d1295d1a2 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -870,6 +870,7 @@ Wallet.prototype.createPaymentTx = function(options, cb) { if (typeof options === 'string') { options = { uri: options }; } + options.uri = options.uri || options.url; if (options.uri.indexOf('bitcoin:') === 0) { options.uri = parseBitcoinURI(options.uri).merchant; @@ -906,11 +907,13 @@ Wallet.prototype.fetchPaymentTx = function(options, cb) { if (typeof options === 'string') { options = { uri: options }; } + options.uri = options.uri || options.url; options.fetch = true; + if (this.paymentRequests[options.uri]) { + return cb(null, this.paymentRequests[options.uri].merchantData); + } return this.createPaymentTx(options, function(err, merchantData, options, pr) { - var id = self.hashMerchantData(merchantData); - self.paymentRequests[id] = { - id: id, + self.paymentRequests[options.uri] = { merchantData: merchantData, options: options, pr: pr @@ -919,17 +922,13 @@ Wallet.prototype.fetchPaymentTx = function(options, cb) { }); }; -Wallet.hashMerchantData = -Wallet.prototype.hashMerchantData = function(merchantData) { - return merchantData.request_url - + ':' + merchantData.pr.payment_url - + ':' + merchantData.total - + ':' + merchantData.pr.pd.merchant_data; -}; - Wallet.prototype.receivePaymentRequest = function(options, pr, cb) { var self = this; + if (this.paymentRequests[options.uri]) { + delete this.paymentRequests[options.uri]; + } + var ver = pr.get('payment_details_version'); var pki_type = pr.get('pki_type'); var pki_data = pr.get('pki_data'); From 2aee44f31cf1dcfd6449ae3b21e4c3663610f4b5 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Thu, 31 Jul 2014 12:37:47 -0700 Subject: [PATCH 045/178] paypro: refactor cached payment requests. --- js/controllers/send.js | 16 ++++------------ js/models/core/Wallet.js | 23 ++++++++++++++++------- 2 files changed, 20 insertions(+), 19 deletions(-) diff --git a/js/controllers/send.js b/js/controllers/send.js index a9d9995cf..ee280ee5d 100644 --- a/js/controllers/send.js +++ b/js/controllers/send.js @@ -94,18 +94,10 @@ angular.module('copayApp.controllers').controller('SendController', && copay.HDPath.parseBitcoinURI(address); if (uri && uri.merchant) { - var req = w.paymentRequests[uri.merchant]; - if (req) { - if (commentText) { - req.options.memo = commentText; - } - w.receivePaymentRequest(req.options, req.pr, done); - } else { - w.createPaymentTx({ - uri: uri.merchant, - memo: commentText - }, done); - } + w.createPaymentTx({ + uri: uri.merchant, + memo: commentText + }, done); } else { w.createTx(address, amount, commentText, done); } diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index d1295d1a2..6cd173c49 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -879,9 +879,18 @@ Wallet.prototype.createPaymentTx = function(options, cb) { } } + var req = this.paymentRequests[options.uri]; + if (req) { + req.options.memo = options.memo; + req.options.fetch = false; + delete this.paymentRequests[options.uri]; + this.receivePaymentRequest(req.options, req.pr, cb); + return; + } + return $http({ method: options.method || 'POST', - url: options.uri || options.url, + url: options.uri, headers: { 'Accept': PayPro.PAYMENT_REQUEST_CONTENT_TYPE + ', ' + PayPro.PAYMENT_ACK_CONTENT_TYPE, @@ -903,15 +912,19 @@ Wallet.prototype.createPaymentTx = function(options, cb) { Wallet.prototype.fetchPaymentTx = function(options, cb) { var self = this; + options = options || {}; if (typeof options === 'string') { options = { uri: options }; } options.uri = options.uri || options.url; options.fetch = true; - if (this.paymentRequests[options.uri]) { - return cb(null, this.paymentRequests[options.uri].merchantData); + + var req = this.paymentRequests[options.uri]; + if (req) { + return cb(null, req.merchantData); } + return this.createPaymentTx(options, function(err, merchantData, options, pr) { self.paymentRequests[options.uri] = { merchantData: merchantData, @@ -925,10 +938,6 @@ Wallet.prototype.fetchPaymentTx = function(options, cb) { Wallet.prototype.receivePaymentRequest = function(options, pr, cb) { var self = this; - if (this.paymentRequests[options.uri]) { - delete this.paymentRequests[options.uri]; - } - var ver = pr.get('payment_details_version'); var pki_type = pr.get('pki_type'); var pki_data = pr.get('pki_data'); From 6e9eaf7d9d70573eae80155d994c45bf7d82b0c5 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Thu, 31 Jul 2014 13:16:03 -0700 Subject: [PATCH 046/178] paypro: cached payment requests. validation. --- js/controllers/send.js | 23 +++++++++++++++++++---- js/directives.js | 22 ++++++++++++---------- js/models/core/Wallet.js | 10 ++++------ 3 files changed, 35 insertions(+), 20 deletions(-) diff --git a/js/controllers/send.js b/js/controllers/send.js index ee280ee5d..3aa370f20 100644 --- a/js/controllers/send.js +++ b/js/controllers/send.js @@ -64,27 +64,42 @@ angular.module('copayApp.controllers').controller('SendController', function done(ntxid, ca) { var txp = w.txProposals.txps[ntxid]; var merchantData = txp.merchant; + var amt = angular.element(document.querySelector('input#amount')); if (w.isShared()) { $scope.loading = false; var message = 'The transaction proposal has been created'; if (ca) { - message += '.\nThis payment protocol transaction' - + 'has been verified through ' + ca + '.'; + message += '\nThis payment protocol transaction' + + ' has been verified through ' + ca + '.'; + } + if (merchantData) { + message += '\nFor merchant: ' + merchantData.pr.payment_url; } notification.success('Success!', message); $scope.loadTxs(); + if (merchantData) { + amt.attr('disabled', false); + } } else { w.sendTx(ntxid, function(txid, ca) { if (txid) { - notification.success('Transaction broadcast', 'Transaction id: ' + txid); + var message = 'Transaction id: ' + txid; if (ca) { - notification.success('Root Certificate', ca); + message += '\nThis payment protocol transaction' + + ' has been verified through ' + ca + '.'; } + if (merchantData) { + message += '\nFor merchant: ' + merchantData.pr.payment_url; + } + notification.success('Transaction broadcast', message); } else { notification.error('Error', 'There was an error sending the transaction.'); } $scope.loading = false; $scope.loadTxs(); + if (merchantData) { + amt.attr('disabled', false); + } }); } $rootScope.pendingPayment = null; diff --git a/js/directives.js b/js/directives.js index aed56933d..19d1ce9a7 100644 --- a/js/directives.js +++ b/js/directives.js @@ -1,8 +1,6 @@ 'use strict'; angular.module('copayApp.directives') - //.directive('validAddress', ['$rootScope', - //function($rootScope) { .directive('validAddress', [ function() { @@ -13,17 +11,20 @@ angular.module('copayApp.directives') require: 'ngModel', link: function(scope, elem, attrs, ctrl) { var validator = function(value) { - // Is payment protocol address? var uri = copay.HDPath.parseBitcoinURI(value); + // Is this a payment protocol URI (BIP-72)? if (uri && uri.merchant) { scope.wallet.fetchPaymentTx(uri.merchant, function(err, merchantData) { - var txp = { merchant: merchantData }; + if (err) { + // XXX where would we send this error? + return; + } - var expires = new Date(txp.merchant.pr.expires * 1000); - var memo = txp.merchant.pr.memo; - var payment_url = txp.merchant.pr.payment_url; - var total = txp.merchant.total; + var expires = new Date(merchantData.pr.expires * 1000); + var memo = merchantData.pr.memo; + var payment_url = merchantData.pr.payment_url; + var total = merchantData.total; if (typeof total === 'string') { total = bitcore.bignum(total, 10).toBuffer(); @@ -46,14 +47,15 @@ angular.module('copayApp.directives') var tamount = angular.element( document.querySelector('div.send-note > p[ng-class]:nth-of-type(2)')); tamount.attr('class', - tamount.attr('class').replace(' hidden', '')) + tamount.attr('class').replace('hidden', '').trim()) tamount.text(total + ' (CA: ' + ca + '. Expires: ' + expires.toISOString() + '): ' + memo); + + ctrl.$setValidity('validAddress', true); }); - ctrl.$setValidity('validAddress', true); return 'Merchant: '+ uri.merchant; } diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index 6cd173c49..b205a08dc 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -881,10 +881,8 @@ Wallet.prototype.createPaymentTx = function(options, cb) { var req = this.paymentRequests[options.uri]; if (req) { - req.options.memo = options.memo; - req.options.fetch = false; delete this.paymentRequests[options.uri]; - this.receivePaymentRequest(req.options, req.pr, cb); + this.receivePaymentRequest(options, req.pr, cb); return; } @@ -925,10 +923,10 @@ Wallet.prototype.fetchPaymentTx = function(options, cb) { return cb(null, req.merchantData); } - return this.createPaymentTx(options, function(err, merchantData, options, pr) { + return this.createPaymentTx(options, function(err, merchantData, pr) { + if (err) return cb(err); self.paymentRequests[options.uri] = { merchantData: merchantData, - options: options, pr: pr }; return cb(null, merchantData); @@ -1020,7 +1018,7 @@ Wallet.prototype.receivePaymentRequest = function(options, pr, cb) { return this.getUnspent(function(err, unspent) { if (options.fetch) { self.createPaymentTxSync(options, merchantData, unspent); - return cb(null, merchantData, options, pr); + return cb(null, merchantData, pr); } var ntxid = self.createPaymentTxSync(options, merchantData, unspent); From 7b678a91b554fddd4bf1a2ff7307565db782c34e Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Thu, 31 Jul 2014 14:41:50 -0700 Subject: [PATCH 047/178] paypro: misc work. --- app.js | 65 +++++----------------------------------- js/directives.js | 3 +- js/models/core/Wallet.js | 14 ++++----- 3 files changed, 17 insertions(+), 65 deletions(-) diff --git a/app.js b/app.js index 4714da0f8..7c0f08a4d 100644 --- a/app.js +++ b/app.js @@ -14,73 +14,24 @@ app.start = function(port, callback) { if (process.env.USE_HTTPS) { process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = '0'; - var fs = require('fs'); var path = require('path'); var bc = path.dirname(require.resolve('bitcore/package.json')); - var server = require('https').createServer({ - key: fs.readFileSync(bc + '/test/data/x509.key'), - cert: fs.readFileSync(bc + '/test/data/x509.crt') - }); + // var fs = require('fs'); + // var server = require('https').createServer({ + // key: fs.readFileSync(bc + '/test/data/x509.key'), + // cert: fs.readFileSync(bc + '/test/data/x509.crt') + // }); var pserver = require(bc + '/examples/PayPro/server.js'); pserver.removeListener('request', pserver.app); pserver.on('request', function(req, res) { - var statusCode = res.statusCode; - - var headers = Object.keys(res._headers || {}).reduce(function(out, key) { - out[key] = res._headers[key]; - return out; - }, {}); - - var headerNames = Object.keys(res._headerNames || {}).reduce(function(out, key) { - out[key] = res._headerNames[key]; - return out; - }, {}); - - var writeHead = res.writeHead; - var write = res.write; - var end = res.end; - var status; - - res.writeHead = function(s) { - status = s; - if (status > 400) { - return; - } - return writeHead.apply(this, arguments); - }; - - res.write = function() { - if (status && status > 400) { - return true; - } - return write.apply(this, arguments); - }; - - res.end = function() { - var self = this; - var args = Array.prototype.slice.call(arguments); - process.nextTick(function() { - self.statusCode = statusCode; - self._headers = headers; - self._headerNames = headerNames; - self.writeHead = writeHead; - self.write = write; - self.end = end; - if ((status || self.statusCode) > 400) { - return pserver.app(req, res); - } - return end.apply(self, args); - }); - return true; - }; - + if (req.url.indexOf('/-/') === 0) { + return pserver.app(req, res); + } return app(req, res); }); - pserver.listen(port, function() { callback('https://localhost:' + port); }); - return; } diff --git a/js/directives.js b/js/directives.js index 19d1ce9a7..e8b5dd929 100644 --- a/js/directives.js +++ b/js/directives.js @@ -53,9 +53,10 @@ angular.module('copayApp.directives') + expires.toISOString() + '): ' + memo); - ctrl.$setValidity('validAddress', true); + // ctrl.$setValidity('validAddress', true); }); + ctrl.$setValidity('validAddress', true); return 'Merchant: '+ uri.merchant; } diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index b205a08dc..e34df0dc2 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -32,10 +32,10 @@ if (typeof window !== 'undefined') { } if (typeof angular !== 'undefined') { - var $http = G.$http || angular.bootstrap().get('$http'); + G.$http = G.$http || angular.bootstrap().get('$http'); } -var $http = G.$http || function $http(options, callback) { +G.$http = function $http(options, callback) { if (typeof options === 'string') { options = { uri: options }; } @@ -84,11 +84,11 @@ var $http = G.$http || function $http(options, callback) { // Newer browsers: xhr.responseType = 'arraybuffer'; - xhr.onreadystatechange = function() { - if (xhr.readyState == 4) { - ; - } - }; + // xhr.onreadystatechange = function() { + // if (xhr.readyState == 4) { + // ; + // } + // }; xhr.onload = function(event) { var response = xhr.response; From 91b2d9dd02d0a5144a56b06fe1aab2437c42bd33 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Thu, 31 Jul 2014 16:33:09 -0700 Subject: [PATCH 048/178] paypro: fix endianess. fix fields. fix elements in a messy way. --- js/controllers/send.js | 6 ++++++ js/directives.js | 31 +++++++++++++++++++++++-------- js/models/core/Wallet.js | 30 ++++++++++++++++++++++-------- 3 files changed, 51 insertions(+), 16 deletions(-) diff --git a/js/controllers/send.js b/js/controllers/send.js index 3aa370f20..22a92fb4f 100644 --- a/js/controllers/send.js +++ b/js/controllers/send.js @@ -65,6 +65,8 @@ angular.module('copayApp.controllers').controller('SendController', var txp = w.txProposals.txps[ntxid]; var merchantData = txp.merchant; var amt = angular.element(document.querySelector('input#amount')); + var submit = angular.element(document.querySelector('button[type=submit]')); + var sendall = angular.element(document.querySelector('[title="Send all funds"]')); if (w.isShared()) { $scope.loading = false; var message = 'The transaction proposal has been created'; @@ -79,6 +81,8 @@ angular.module('copayApp.controllers').controller('SendController', $scope.loadTxs(); if (merchantData) { amt.attr('disabled', false); + submit.attr('disabled', true); + sendall.attr('class', sendall.attr('class').replace(' hidden', '')); } } else { w.sendTx(ntxid, function(txid, ca) { @@ -99,6 +103,8 @@ angular.module('copayApp.controllers').controller('SendController', $scope.loadTxs(); if (merchantData) { amt.attr('disabled', false); + submit.attr('disabled', true); + sendall.attr('class', sendall.attr('class').replace(' hidden', '')); } }); } diff --git a/js/directives.js b/js/directives.js index e8b5dd929..2309904a5 100644 --- a/js/directives.js +++ b/js/directives.js @@ -6,6 +6,7 @@ angular.module('copayApp.directives') var bitcore = require('bitcore'); var Address = bitcore.Address; + var bignum = bitcore.Bignum; return { require: 'ngModel', @@ -21,17 +22,23 @@ angular.module('copayApp.directives') return; } - var expires = new Date(merchantData.pr.expires * 1000); - var memo = merchantData.pr.memo; - var payment_url = merchantData.pr.payment_url; + var expires = new Date(merchantData.pr.pd.expires * 1000); + var memo = merchantData.pr.pd.memo; + var payment_url = merchantData.pr.pd.payment_url; var total = merchantData.total; if (typeof total === 'string') { - total = bitcore.bignum(total, 10).toBuffer(); + total = bignum(total, 10).toBuffer({ + endian: 'little', + size: 1 + }); } - total = bitcore - .bignum.fromBuffer(total) + total = bignum + .fromBuffer(total, { + endian: 'little', + size: 1 + }) .div(config.unitToSatoshi) .toString(10); @@ -47,12 +54,20 @@ angular.module('copayApp.directives') var tamount = angular.element( document.querySelector('div.send-note > p[ng-class]:nth-of-type(2)')); tamount.attr('class', - tamount.attr('class').replace('hidden', '').trim()) - tamount.text(total + ' (CA: ' + ca + tamount.attr('class').replace(' hidden', '')) + tamount.text(total + ' (CA: ' + merchantData.pr.ca + '. Expires: ' + expires.toISOString() + '): ' + memo); + var submit = angular.element( + document.querySelector('button[type=submit]')); + submit.attr('disabled', false); + + var sendall = angular.element( + document.querySelector('[title="Send all funds"]')); + sendall.attr('class', sendall.attr('class') + ' hidden'); + // ctrl.$setValidity('validAddress', true); }); diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index e34df0dc2..127fc3056 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -1004,17 +1004,20 @@ Wallet.prototype.receivePaymentRequest = function(options, pr, cb) { }), time: time, expires: expires, - memo: memo, + memo: memo || 'No Message', payment_url: payment_url, merchant_data: merchant_data.toString('hex') }, signature: sig, ca: ca, }, - request_url: options.uri || options.url, + request_url: options.uri, total: bignum('0', 10).toString(10) }; + console.log('receivePaymentRequest'); + console.log(merchantData); + return this.getUnspent(function(err, unspent) { if (options.fetch) { self.createPaymentTxSync(options, merchantData, unspent); @@ -1059,10 +1062,12 @@ Wallet.prototype.sendPaymentTx = function(ntxid, options, cb) { || self.publicKeyRing.getPubKeys(0, false, this.getMyCopayerId())[0]; if (options.refund_to) { - var total = bignum('0', 10); - for (var i = 0; i < tx.outs.length - 1; i++) { - total = total.add(bignum.fromBuffer(tx.outs[i].v)); - } + var total = txp.merchant.pr.pd.outputs.reduce(function(total, _, i) { + return total.add(bignum.fromBuffer(tx.outs[i].v, { + endian: 'little', + size: 1 + })); + }, bugnum('0', 10)); var rpo = new PayPro(); rpo = rpo.makeOutput(); rpo.set('amount', +total.toString(10)); @@ -1164,7 +1169,7 @@ Wallet.prototype.createPaymentTxSync = function(options, merchantData, unspent) merchantData.pr.pd.outputs.forEach(function(output) { outs.push({ address: self.getAddressesStr()[0] - || 'mfWxJ45yp2SFn7UciZyNpvDKrzbhyfKrY8', // dummy address (testnet 0 * hash160) + || '2N6J45pqfu5y7zgWDwXDAmdd8qzK1oRdz3A', // dummy address (testnet 0 * hash160) amountSatStr: '0' // dummy amount }); }); @@ -1185,6 +1190,9 @@ Wallet.prototype.createPaymentTxSync = function(options, merchantData, unspent) var signed = b.sign(keys); } + console.log('createPaymentTxSync:1'); + console.log(merchantData); + if (typeof merchantData.total === 'string') { merchantData.total = bignum(merchantData.total, 10); } @@ -1217,11 +1225,17 @@ Wallet.prototype.createPaymentTxSync = function(options, merchantData, unspent) b.tx.outs[i].v = v; b.tx.outs[i].s = s; - merchantData.total = merchantData.total.add(bignum.fromBuffer(v)); + merchantData.total = merchantData.total.add(bignum.fromBuffer(v, { + endian: 'little', + size: 1 + })); }); merchantData.total = merchantData.total.toString(10); + console.log('createPaymentTxSync:2'); + console.log(merchantData); + if (options.fetch) return; this.log(''); From f697e2ffbb2031720f600c58b7d879038553d8cb Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Thu, 31 Jul 2014 16:38:08 -0700 Subject: [PATCH 049/178] paypro: show untrusted certs. --- js/directives.js | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/js/directives.js b/js/directives.js index 2309904a5..9a4b39068 100644 --- a/js/directives.js +++ b/js/directives.js @@ -47,15 +47,17 @@ angular.module('copayApp.directives') amount.val(total); amount.attr('disabled', true); - var sendto = angular.element( - document.querySelector('div.send-note > p[ng-class]:first-of-type')); + var sendto = angular.element(document + .querySelector('div.send-note > p[ng-class]:first-of-type')); sendto.html(sendto.html() + '
Server: ' + memo); - var tamount = angular.element( - document.querySelector('div.send-note > p[ng-class]:nth-of-type(2)')); + var tamount = angular.element(document + .querySelector('div.send-note > p[ng-class]:nth-of-type(2)')); + var ca = merchantData.pr.ca + || 'Untrusted'; tamount.attr('class', tamount.attr('class').replace(' hidden', '')) - tamount.text(total + ' (CA: ' + merchantData.pr.ca + tamount.html(total + ' (CA: ' + ca + '. Expires: ' + expires.toISOString() + '): ' + memo); @@ -72,6 +74,7 @@ angular.module('copayApp.directives') }); ctrl.$setValidity('validAddress', true); + return 'Merchant: '+ uri.merchant; } From d94e8525fd0bee7670389d9c318f36542fac3a57 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Thu, 31 Jul 2014 16:46:34 -0700 Subject: [PATCH 050/178] paypro: all working except for value input. --- js/models/core/Wallet.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index 127fc3056..8c47db7d8 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -998,7 +998,7 @@ Wallet.prototype.receivePaymentRequest = function(options, pr, cb) { script: { offset: output.get('script').offset, limit: output.get('script').limit, - buffer: output.get('script').buffer.toString('hex') + buffer: new Buffer(output.get('script').buffer).toString('hex') } }; }), From feaa71c951e7a6d542fa7388162bdfc2dd1589b9 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Thu, 31 Jul 2014 17:01:35 -0700 Subject: [PATCH 051/178] paypro: total now working. --- js/directives.js | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/js/directives.js b/js/directives.js index 9a4b39068..195338dca 100644 --- a/js/directives.js +++ b/js/directives.js @@ -34,14 +34,25 @@ angular.module('copayApp.directives') }); } + // XXX good + // total = bignum + // .fromBuffer(total, { + // endian: 'little', + // size: 1 + // }) + // .div(config.unitToSatoshi) + // .toString(10); + total = bignum .fromBuffer(total, { endian: 'little', size: 1 }) - .div(config.unitToSatoshi) .toString(10); + // XXX bad + total = +total / config.unitToSatoshi; + var amount = angular.element( document.querySelector('input#amount')); amount.val(total); @@ -60,7 +71,7 @@ angular.module('copayApp.directives') tamount.html(total + ' (CA: ' + ca + '. Expires: ' + expires.toISOString() - + '): ' + memo); + + ')'); var submit = angular.element( document.querySelector('button[type=submit]')); From 28459c2d4eec3829c25b0f98f2d6d7d2393a8a15 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Thu, 31 Jul 2014 17:02:09 -0700 Subject: [PATCH 052/178] paypro: remove debug code. --- js/models/core/Wallet.js | 9 --------- 1 file changed, 9 deletions(-) diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index 8c47db7d8..5d833309f 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -1015,9 +1015,6 @@ Wallet.prototype.receivePaymentRequest = function(options, pr, cb) { total: bignum('0', 10).toString(10) }; - console.log('receivePaymentRequest'); - console.log(merchantData); - return this.getUnspent(function(err, unspent) { if (options.fetch) { self.createPaymentTxSync(options, merchantData, unspent); @@ -1190,9 +1187,6 @@ Wallet.prototype.createPaymentTxSync = function(options, merchantData, unspent) var signed = b.sign(keys); } - console.log('createPaymentTxSync:1'); - console.log(merchantData); - if (typeof merchantData.total === 'string') { merchantData.total = bignum(merchantData.total, 10); } @@ -1233,9 +1227,6 @@ Wallet.prototype.createPaymentTxSync = function(options, merchantData, unspent) merchantData.total = merchantData.total.toString(10); - console.log('createPaymentTxSync:2'); - console.log(merchantData); - if (options.fetch) return; this.log(''); From a9b522888ed0729e61cbea18bacece02205963b3 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Thu, 31 Jul 2014 17:04:24 -0700 Subject: [PATCH 053/178] Revert "paypro: remove debug code." This reverts commit 019283e04cb6b25f6431cb56f02618c6d9e7fe90. --- js/models/core/Wallet.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index 5d833309f..8c47db7d8 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -1015,6 +1015,9 @@ Wallet.prototype.receivePaymentRequest = function(options, pr, cb) { total: bignum('0', 10).toString(10) }; + console.log('receivePaymentRequest'); + console.log(merchantData); + return this.getUnspent(function(err, unspent) { if (options.fetch) { self.createPaymentTxSync(options, merchantData, unspent); @@ -1187,6 +1190,9 @@ Wallet.prototype.createPaymentTxSync = function(options, merchantData, unspent) var signed = b.sign(keys); } + console.log('createPaymentTxSync:1'); + console.log(merchantData); + if (typeof merchantData.total === 'string') { merchantData.total = bignum(merchantData.total, 10); } @@ -1227,6 +1233,9 @@ Wallet.prototype.createPaymentTxSync = function(options, merchantData, unspent) merchantData.total = merchantData.total.toString(10); + console.log('createPaymentTxSync:2'); + console.log(merchantData); + if (options.fetch) return; this.log(''); From cf674dced20e62525baad1deada9864f835f163e Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Thu, 31 Jul 2014 17:07:33 -0700 Subject: [PATCH 054/178] paypro: remove temporary xhr shim. --- js/models/core/Wallet.js | 84 ---------------------------------------- 1 file changed, 84 deletions(-) diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index 8c47db7d8..81a25fc41 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -35,90 +35,6 @@ if (typeof angular !== 'undefined') { G.$http = G.$http || angular.bootstrap().get('$http'); } -G.$http = function $http(options, callback) { - if (typeof options === 'string') { - options = { uri: options }; - } - - options.method = options.method || 'GET'; - options.headers = options.headers || {}; - - var ret = { - success: function(cb) { - this._success = cb; - return this; - }, - error: function(cb) { - this._error = cb; - return this; - }, - _success: function() { - ; - }, - _error: function(_, err) { - throw err; - } - }; - - var method = (options.method || 'GET').toUpperCase(); - var uri = options.uri || options.url; - var req = options; - - req.headers = req.headers || {}; - req.body = req.body || {}; - - if (typeof XMLHttpRequest !== 'undefined') { - var xhr = new XMLHttpRequest(); - xhr.open(method, uri, true); - - Object.keys(options.headers).forEach(function(key) { - var val = options.headers[key]; - if (key === 'Content-Length') return; - if (key === 'Content-Transfer-Encoding') return; - xhr.setRequestHeader(key, val); - }); - - // For older browsers: - // xhr.overrideMimeType('text/plain; charset=x-user-defined'); - - // Newer browsers: - xhr.responseType = 'arraybuffer'; - - // xhr.onreadystatechange = function() { - // if (xhr.readyState == 4) { - // ; - // } - // }; - - xhr.onload = function(event) { - var response = xhr.response; - var buf = new Uint8Array(response); - var headers = {}; - (xhr.getAllResponseHeaders() || '').replace( - /(?:\r?\n|^)([^:\r\n]+): *([^\r\n]+)/g, - function($0, $1, $2) { - headers[$1.toLowerCase()] = $2; - } - ); - return ret._success(buf, xhr.status, headers, options); - }; - - xhr.onerror = function(event) { - return ret._error(null, new Error(event.message), null, options); - }; - - if (options.body) { - xhr.send(options.body); - } else { - xhr.send(null); - } - - return ret; - } - - return ret; -}; - function Wallet(opts) { var self = this; From 959a2f40cb7af22a2ed394b4155974ecb5291b50 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Thu, 31 Jul 2014 17:13:58 -0700 Subject: [PATCH 055/178] paypro: fix script buffers. --- js/models/core/Wallet.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index 81a25fc41..738ca7951 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -914,7 +914,8 @@ Wallet.prototype.receivePaymentRequest = function(options, pr, cb) { script: { offset: output.get('script').offset, limit: output.get('script').limit, - buffer: new Buffer(output.get('script').buffer).toString('hex') + buffer: new Buffer(new Uint8Array( + output.get('script').buffer)).toString('hex') } }; }), From 413098013325e03806c5677f6b7c0f9779b95596 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Thu, 31 Jul 2014 17:15:19 -0700 Subject: [PATCH 056/178] paypro: remove debug logs again. --- js/models/core/Wallet.js | 9 --------- 1 file changed, 9 deletions(-) diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index 738ca7951..d7e99ff3f 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -932,9 +932,6 @@ Wallet.prototype.receivePaymentRequest = function(options, pr, cb) { total: bignum('0', 10).toString(10) }; - console.log('receivePaymentRequest'); - console.log(merchantData); - return this.getUnspent(function(err, unspent) { if (options.fetch) { self.createPaymentTxSync(options, merchantData, unspent); @@ -1107,9 +1104,6 @@ Wallet.prototype.createPaymentTxSync = function(options, merchantData, unspent) var signed = b.sign(keys); } - console.log('createPaymentTxSync:1'); - console.log(merchantData); - if (typeof merchantData.total === 'string') { merchantData.total = bignum(merchantData.total, 10); } @@ -1150,9 +1144,6 @@ Wallet.prototype.createPaymentTxSync = function(options, merchantData, unspent) merchantData.total = merchantData.total.toString(10); - console.log('createPaymentTxSync:2'); - console.log(merchantData); - if (options.fetch) return; this.log(''); From 38667302187b0c1f909121b6c905f3a4d8325958 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Fri, 1 Aug 2014 14:34:31 -0700 Subject: [PATCH 057/178] paypro: remove old code. --- js/directives.js | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/js/directives.js b/js/directives.js index 195338dca..c2d650b89 100644 --- a/js/directives.js +++ b/js/directives.js @@ -34,15 +34,6 @@ angular.module('copayApp.directives') }); } - // XXX good - // total = bignum - // .fromBuffer(total, { - // endian: 'little', - // size: 1 - // }) - // .div(config.unitToSatoshi) - // .toString(10); - total = bignum .fromBuffer(total, { endian: 'little', @@ -50,7 +41,7 @@ angular.module('copayApp.directives') }) .toString(10); - // XXX bad + // XXX There needs to be a better way to do this: total = +total / config.unitToSatoshi; var amount = angular.element( From 438820ef233eeff2568e5b926507c59ea798226c Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Fri, 1 Aug 2014 14:35:00 -0700 Subject: [PATCH 058/178] paypro: set invalid on errors. --- js/directives.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/js/directives.js b/js/directives.js index c2d650b89..899ee977e 100644 --- a/js/directives.js +++ b/js/directives.js @@ -18,7 +18,7 @@ angular.module('copayApp.directives') if (uri && uri.merchant) { scope.wallet.fetchPaymentTx(uri.merchant, function(err, merchantData) { if (err) { - // XXX where would we send this error? + ctrl.$setValidity('validAddress', false); return; } @@ -72,7 +72,7 @@ angular.module('copayApp.directives') document.querySelector('[title="Send all funds"]')); sendall.attr('class', sendall.attr('class') + ' hidden'); - // ctrl.$setValidity('validAddress', true); + ctrl.$setValidity('validAddress', true); }); ctrl.$setValidity('validAddress', true); From f77d3bc3507b821f416ffb12d30238258aba0ed3 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Fri, 1 Aug 2014 14:39:24 -0700 Subject: [PATCH 059/178] paypro: fix trusted check. --- js/models/core/Wallet.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index d7e99ff3f..0ad696e13 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -872,7 +872,7 @@ Wallet.prototype.receivePaymentRequest = function(options, pr, cb) { var der = cert.toString('hex'); var pem = PayPro.prototype._DERtoPEM(der, 'CERTIFICATE'); return PayPro.RootCerts.getTrusted(pem); - }); + }).filter(Boolean); if (!trusted.length) { if (typeof SSL_UNTRUSTED === 'undefined') { From c471983a5848acc18db08282acc8f9339f2e2bc4 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Fri, 1 Aug 2014 14:52:50 -0700 Subject: [PATCH 060/178] paypro: clean up unnecessary code. --- js/models/core/Wallet.js | 40 +++++++++++++++++++++------------------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index 0ad696e13..d654933ec 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -914,6 +914,8 @@ Wallet.prototype.receivePaymentRequest = function(options, pr, cb) { script: { offset: output.get('script').offset, limit: output.get('script').limit, + // NOTE: For some reason output.script.buffer + // is only an ArrayBuffer buffer: new Buffer(new Uint8Array( output.get('script').buffer)).toString('hex') } @@ -921,12 +923,12 @@ Wallet.prototype.receivePaymentRequest = function(options, pr, cb) { }), time: time, expires: expires, - memo: memo || 'No Message', + memo: memo || 'This server would like some BTC from you.', payment_url: payment_url, merchant_data: merchant_data.toString('hex') }, signature: sig, - ca: ca, + ca: ca }, request_url: options.uri, total: bignum('0', 10).toString(10) @@ -984,6 +986,7 @@ Wallet.prototype.sendPaymentTx = function(ntxid, options, cb) { }, bugnum('0', 10)); var rpo = new PayPro(); rpo = rpo.makeOutput(); + // XXX Bad - the amount *has* to be a Number in protobufjs rpo.set('amount', +total.toString(10)); rpo.set('script', Buffer.concat([ @@ -1046,13 +1049,17 @@ Wallet.prototype.sendPaymentTx = function(ntxid, options, cb) { Wallet.prototype.receivePaymentRequestACK = function(tx, txp, ack, cb) { var self = this; + var payment = ack.get('payment'); var memo = ack.get('memo'); + this.log('Our payment was acknowledged!'); this.log('Message from Merchant: %s', memo); + payment = PayPro.Payment.decode(payment); var pay = new PayPro(); payment = pay.makePayment(payment); + var tx = payment.message.transactions[0]; if (tx.buffer) { tx.buffer = tx.buffer.slice(tx.offset, tx.limit); @@ -1060,6 +1067,7 @@ Wallet.prototype.receivePaymentRequestACK = function(tx, txp, ack, cb) { ptx.parse(tx.buffer); tx = ptx; } + var txid = tx.getHash().toString('hex'); return cb(txid, txp.merchant.pr.ca); }; @@ -1069,9 +1077,10 @@ Wallet.prototype.createPaymentTxSync = function(options, merchantData, unspent) var priv = this.privateKey; var pkr = this.publicKeyRing; - // preconditions.checkArgument(new Address(toAddress).network().name === this.getNetworkName()); preconditions.checkState(pkr.isComplete()); - if (options.memo) preconditions.checkArgument(options.memo.length <= 100); + if (options.memo) { + preconditions.checkArgument(options.memo.length <= 100); + } var opts = { remainderOut: { @@ -1083,7 +1092,7 @@ Wallet.prototype.createPaymentTxSync = function(options, merchantData, unspent) merchantData.pr.pd.outputs.forEach(function(output) { outs.push({ address: self.getAddressesStr()[0] - || '2N6J45pqfu5y7zgWDwXDAmdd8qzK1oRdz3A', // dummy address (testnet 0 * hash160) + || '2N6J45pqfu5y7zgWDwXDAmdd8qzK1oRdz3A', // dummy address amountSatStr: '0' // dummy amount }); }); @@ -1104,22 +1113,15 @@ Wallet.prototype.createPaymentTxSync = function(options, merchantData, unspent) var signed = b.sign(keys); } - if (typeof merchantData.total === 'string') { - merchantData.total = bignum(merchantData.total, 10); - } + merchantData.total = bignum(merchantData.total, 10); merchantData.pr.pd.outputs.forEach(function(output, i) { - var amount = output.get - ? output.get('amount') - : output.amount; - - var script = output.get - ? output.get('script') - : { - offset: output.script.offset, - limit: output.script.limit, - buffer: new Buffer(output.script.buffer, 'hex') - }; + var amount = output.amount; + var script = { + offset: output.script.offset, + limit: output.script.limit, + buffer: new Buffer(output.script.buffer, 'hex') + }; var v = new Buffer(8); v[0] = (amount.low >> 0) & 0xff; From 3a73f2453cd2026a0d0cb307a7ede909e33e3e52 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Fri, 1 Aug 2014 15:00:38 -0700 Subject: [PATCH 061/178] paypro: ripesha refund_to pubkeys. --- js/models/core/Wallet.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index d654933ec..cbc4cf2d7 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -978,6 +978,8 @@ Wallet.prototype.sendPaymentTx = function(ntxid, options, cb) { || self.publicKeyRing.getPubKeys(0, false, this.getMyCopayerId())[0]; if (options.refund_to) { + // pubkey needs to be ripesha'd + options.refund_to = bitcore.sha256ripe160(options.refund_to); var total = txp.merchant.pr.pd.outputs.reduce(function(total, _, i) { return total.add(bignum.fromBuffer(tx.outs[i].v, { endian: 'little', From 53abfcacda124d781c79061178f9d8842fc157cd Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Fri, 1 Aug 2014 15:01:43 -0700 Subject: [PATCH 062/178] paypro: remove more unnecessary code. --- js/models/core/Wallet.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index cbc4cf2d7..78cd2b68f 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -1012,9 +1012,7 @@ Wallet.prototype.sendPaymentTx = function(ntxid, options, cb) { var pay = new PayPro(); pay = pay.makePayment(); var merchant_data = txp.merchant.pr.pd.merchant_data; - if (typeof merchant_data === 'string') { - merchant_data = new Buffer(merchant_data, 'hex'); - } + merchant_data = new Buffer(merchant_data, 'hex'); pay.set('merchant_data', merchant_data); pay.set('transactions', [tx.serialize()]); pay.set('refund_to', refund_outputs); From 6ef3cf29926f2c584ad1390622758f940287ad40 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Fri, 1 Aug 2014 15:03:17 -0700 Subject: [PATCH 063/178] paypro: set memo on comment properly. --- js/models/core/Wallet.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index 78cd2b68f..318dc48cf 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -1020,7 +1020,7 @@ Wallet.prototype.sendPaymentTx = function(ntxid, options, cb) { options.memo = options.memo || options.comment || 'Hi server, I would like to give you some money.'; - pay.set('memo', txp.merchant.pr.pd.memo); + pay.set('memo', options.memo); return $http({ method: 'POST', From fd8f192dbd8879a63974468b3be98a7a85553465 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Fri, 1 Aug 2014 15:55:21 -0700 Subject: [PATCH 064/178] paypro: minor --- js/models/core/Wallet.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index 318dc48cf..47646ab03 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -975,7 +975,7 @@ Wallet.prototype.sendPaymentTx = function(ntxid, options, cb) { var refund_outputs = []; options.refund_to = options.refund_to - || self.publicKeyRing.getPubKeys(0, false, this.getMyCopayerId())[0]; + || this.publicKeyRing.getPubKeys(0, false, this.getMyCopayerId())[0]; if (options.refund_to) { // pubkey needs to be ripesha'd From 8cb2a96aff2e4cdb5f672e3fe4f4b9b8dd8db37e Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Fri, 1 Aug 2014 16:07:56 -0700 Subject: [PATCH 065/178] paypro: do not return error on untrusted cert. let peer decide whether to trust. --- js/models/core/Wallet.js | 11 +++++------ test/test.PayPro.js | 5 ----- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index 47646ab03..66079d834 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -874,11 +874,9 @@ Wallet.prototype.receivePaymentRequest = function(options, pr, cb) { return PayPro.RootCerts.getTrusted(pem); }).filter(Boolean); - if (!trusted.length) { - if (typeof SSL_UNTRUSTED === 'undefined') { - return cb(new Error('Not a trusted certificate.')); - } - } + // if (!trusted.length) { + // return cb(new Error('Not a trusted certificate.')); + // } // Verify Signature var verified = pr.verify(); @@ -928,7 +926,8 @@ Wallet.prototype.receivePaymentRequest = function(options, pr, cb) { merchant_data: merchant_data.toString('hex') }, signature: sig, - ca: ca + ca: ca, + untrusted: !ca }, request_url: options.uri, total: bignum('0', 10).toString(10) diff --git a/test/test.PayPro.js b/test/test.PayPro.js index eb819bff2..fcb3605dd 100644 --- a/test/test.PayPro.js +++ b/test/test.PayPro.js @@ -22,15 +22,10 @@ var Address = bitcore.Address; var PayPro = bitcore.PayPro; var startServer = require('./mocks/FakePayProServer'); -var G = is_browser ? window : global; -G.SSL_UNTRUSTED = true; - var server; describe('PayPro (in Wallet) model', function() { var config = { - // requiredCopayers: 3, - // totalCopayers: 5, requiredCopayers: 1, totalCopayers: 1, spendUnconfirmed: true, From 18ca40b01ee6f0b76354699b9ffca9ae6e30d698 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Fri, 1 Aug 2014 16:30:26 -0700 Subject: [PATCH 066/178] paypro: reset form if user removes payment uri. --- js/directives.js | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/js/directives.js b/js/directives.js index 899ee977e..d52143949 100644 --- a/js/directives.js +++ b/js/directives.js @@ -44,6 +44,9 @@ angular.module('copayApp.directives') // XXX There needs to be a better way to do this: total = +total / config.unitToSatoshi; + var address = angular.element( + document.querySelector('input#address')); + var amount = angular.element( document.querySelector('input#amount')); amount.val(total); @@ -72,6 +75,23 @@ angular.module('copayApp.directives') document.querySelector('[title="Send all funds"]')); sendall.attr('class', sendall.attr('class') + ' hidden'); + address.on('change', function(ev) { + var val = address.val(); + var uri = copay.HDPath.parseBitcoinURI(val || ''); + if (!uri || !uri.merchant) { + if (amount.attr('disabled') === true) { + amount.attr('disabled', false); + } + if (amount.attr('disabled') === false) { + submit.attr('disabled', true); + } + if (/ hidden$/.test(sendall.attr('class'))) { + sendall.attr('class', + sendall.attr('class').replace(' hidden', '')); + } + } + }); + ctrl.$setValidity('validAddress', true); }); From d79dfb20c36c307ed6688a383d278245d29cfdb9 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Fri, 1 Aug 2014 17:32:06 -0700 Subject: [PATCH 067/178] paypro: comment --- js/directives.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/js/directives.js b/js/directives.js index d52143949..4a76f80a3 100644 --- a/js/directives.js +++ b/js/directives.js @@ -90,6 +90,8 @@ angular.module('copayApp.directives') sendall.attr('class').replace(' hidden', '')); } } + // TODO: Check paymentRequest expiration, + // delete if beyond expiration date. }); ctrl.$setValidity('validAddress', true); From d9c72392bd6abd5347710ccea160c8d54f961db8 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Fri, 1 Aug 2014 19:45:24 -0700 Subject: [PATCH 068/178] paypro: through a lot of debugging. Payment sending is working. --- js/controllers/send.js | 8 +- js/directives.js | 33 +++++++- js/models/core/Wallet.js | 159 ++++++++++++++++++++++++++++++++++----- 3 files changed, 180 insertions(+), 20 deletions(-) diff --git a/js/controllers/send.js b/js/controllers/send.js index 22a92fb4f..d210d368f 100644 --- a/js/controllers/send.js +++ b/js/controllers/send.js @@ -111,8 +111,12 @@ angular.module('copayApp.controllers').controller('SendController', $rootScope.pendingPayment = null; } - var uri = address.indexOf('bitcoin:') === 0 - && copay.HDPath.parseBitcoinURI(address); + var uri; + if (address.indexOf('bitcoin:') === 0) { + uri = copay.HDPath.parseBitcoinURI(address); + } else if (address.indexOf('Merchant: ') === 0) { + uri = { merchant: address.split(' ')[1] }; + } if (uri && uri.merchant) { w.createPaymentTx({ diff --git a/js/directives.js b/js/directives.js index 4a76f80a3..7c09521ac 100644 --- a/js/directives.js +++ b/js/directives.js @@ -14,6 +14,8 @@ angular.module('copayApp.directives') var validator = function(value) { var uri = copay.HDPath.parseBitcoinURI(value); + window._rootScope = scope; + // Is this a payment protocol URI (BIP-72)? if (uri && uri.merchant) { scope.wallet.fetchPaymentTx(uri.merchant, function(err, merchantData) { @@ -44,6 +46,9 @@ angular.module('copayApp.directives') // XXX There needs to be a better way to do this: total = +total / config.unitToSatoshi; + var sendForm = angular.element( + document.getElementsByName('sendForm')[0]); + var address = angular.element( document.querySelector('input#address')); @@ -75,7 +80,13 @@ angular.module('copayApp.directives') document.querySelector('[title="Send all funds"]')); sendall.attr('class', sendall.attr('class') + ' hidden'); - address.on('change', function(ev) { + // Reset all the changes from the payment protocol weirdness. + // XXX use ng-change attr instead + //address.attr('ng-change', 'ppChange()'); + //scope.ppChange = scope.ppChange || function() { + //address.on('change', function(ev) { + scope.$on('change', function(ev) { + //scope.$watch('address', function(newValue, oldValue) { var val = address.val(); var uri = copay.HDPath.parseBitcoinURI(val || ''); if (!uri || !uri.merchant) { @@ -85,6 +96,13 @@ angular.module('copayApp.directives') if (amount.attr('disabled') === false) { submit.attr('disabled', true); } + sendto.html(sendto.html().replace(/
Server:.*$/, '')); + if (!/hidden/.test(tamount.attr('class'))) { + tamount.attr(tamount.attr('class') + ' hidden'); + } + if (submit.attr('disabled') === false) { + submit.attr('disabled', true); + } if (/ hidden$/.test(sendall.attr('class'))) { sendall.attr('class', sendall.attr('class').replace(' hidden', '')); @@ -92,9 +110,22 @@ angular.module('copayApp.directives') } // TODO: Check paymentRequest expiration, // delete if beyond expiration date. + //}; + //}); }); + //scope.$apply(); // scope.$digest(); ctrl.$setValidity('validAddress', true); + + scope.sendForm.$valid = true; + scope.sendForm.$invalid = false; + scope.sendForm.$pristine = true; + scope.sendForm.address.$valid = true; + scope.sendForm.address.$invalid = false; + scope.sendForm.address.$pristine = true; + scope.sendForm.amount.$valid = true; + scope.sendForm.amount.$invalid = false; + scope.sendForm.amount.$pristine = true; }); ctrl.$setValidity('validAddress', true); diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index 66079d834..c1ceab8c9 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -25,15 +25,9 @@ var TxProposals = require('./TxProposals'); var PrivateKey = require('./PrivateKey'); var copayConfig = require('../../../config'); -if (typeof window !== 'undefined') { - var G = window; -} else { - var G = global; -} - -if (typeof angular !== 'undefined') { - G.$http = G.$http || angular.bootstrap().get('$http'); -} +var G = typeof window !== 'undefined' + ? window + : global; function Wallet(opts) { var self = this; @@ -808,8 +802,9 @@ Wallet.prototype.createPaymentTx = function(options, cb) { headers: { 'Accept': PayPro.PAYMENT_REQUEST_CONTENT_TYPE + ', ' + PayPro.PAYMENT_ACK_CONTENT_TYPE, - 'Content-Type': 'application/octet-stream', - 'Content-Length': 0 + 'Content-Type': 'application/octet-stream' + // XHR does not allow these: + // 'Content-Length': 0 }, responseType: 'arraybuffer' }) @@ -978,13 +973,13 @@ Wallet.prototype.sendPaymentTx = function(ntxid, options, cb) { if (options.refund_to) { // pubkey needs to be ripesha'd - options.refund_to = bitcore.sha256ripe160(options.refund_to); + options.refund_to = bitcore.util.sha256ripe160(options.refund_to); var total = txp.merchant.pr.pd.outputs.reduce(function(total, _, i) { return total.add(bignum.fromBuffer(tx.outs[i].v, { endian: 'little', size: 1 })); - }, bugnum('0', 10)); + }, bignum('0', 10)); var rpo = new PayPro(); rpo = rpo.makeOutput(); // XXX Bad - the amount *has* to be a Number in protobufjs @@ -1021,6 +1016,29 @@ Wallet.prototype.sendPaymentTx = function(ntxid, options, cb) { pay.set('memo', options.memo); + pay = pay.serialize(); + + this.log(pay); + this.log(pay.toString('hex')); + + // https://www.google.com/search?q=angular+%24http+ArrayBuffer+in+body + // https://github.com/feross/buffer/blob/master/index.js + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Typed_arrays + + // var view = new Uint8Array(new ArrayBuffer(pay.length)); + // Buffer._augment(view); + // pay = pay.copy(view); + + // var view = new Uint8Array(new ArrayBuffer(pay.length)); + // view.set(Array.prototype.slice.call(pay), 0); + // pay = view; + + var buf = new ArrayBuffer(pay.length); + var view = new Uint8Array(buf); + for (var i = 0; i < pay.length; i++) { + view[i] = pay[i]; + } + return $http({ method: 'POST', url: txp.merchant.pr.pd.payment_url, @@ -1028,11 +1046,16 @@ Wallet.prototype.sendPaymentTx = function(ntxid, options, cb) { // BIP-71 'Accept': PayPro.PAYMENT_REQUEST_CONTENT_TYPE + ', ' + PayPro.PAYMENT_ACK_CONTENT_TYPE, - 'Content-Type': PayPro.PAYMENT_CONTENT_TYPE, - 'Content-Length': pay.length + '', - 'Content-Transfer-Encoding': 'binary' + 'Content-Type': PayPro.PAYMENT_CONTENT_TYPE + // XHR does not allow these: + // 'Content-Length': (pay.byteLength || pay.length) + '', + // 'Content-Transfer-Encoding': 'binary' }, - body: pay.serialize(), + // data: pay, + // data: pay, + // data: view, + data: buf, // Technically how this should be done. + // requestType: 'arraybuffer', responseType: 'arraybuffer' }) .success(function(data, status, headers, config) { @@ -1060,7 +1083,13 @@ Wallet.prototype.receivePaymentRequestACK = function(tx, txp, ack, cb) { payment = pay.makePayment(payment); var tx = payment.message.transactions[0]; + + if (!tx) { + return cb(); + } + 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); @@ -1551,4 +1580,100 @@ Wallet.prototype.verifySignedJson = function(senderId, payload, signature) { return v; } +// NOTE: Angular $http module does not send ArrayBuffers correctly, so we're +// not going to use it. We'll have to write our own. Otherwise, we could +// hex-encoded our messages and decode them on the other side, but that +// deviates from BIP-70 slightly. +// if (typeof angular !== 'undefined') { +// G.$http = G.$http || angular.bootstrap().get('$http'); +// } + +G.$http = G.$http || function $http(options, callback) { + if (typeof options === 'string') { + options = { uri: options }; + } + + options.method = options.method || 'GET'; + options.headers = options.headers || {}; + + var ret = { + success: function(cb) { + this._success = cb; + return this; + }, + error: function(cb) { + this._error = cb; + return this; + }, + _success: function() { + ; + }, + _error: function(_, err) { + throw err; + } + }; + + var method = (options.method || 'GET').toUpperCase(); + var uri = options.uri || options.url; + var req = options; + + req.headers = req.headers || {}; + req.body = req.body || {}; + + if (typeof XMLHttpRequest !== 'undefined') { + var xhr = new XMLHttpRequest(); + xhr.open(method, uri, true); + + Object.keys(options.headers).forEach(function(key) { + var val = options.headers[key]; + if (key === 'Content-Length') return; + if (key === 'Content-Transfer-Encoding') return; + xhr.setRequestHeader(key, val); + }); + + // For older browsers (binary data): + // xhr.overrideMimeType('text/plain; charset=x-user-defined'); + + // Newer browsers (binary data): + // xhr.responseType = 'arraybuffer'; + + if (options.responseType) { + xhr.responseType = options.responseType; + } + + // xhr.onreadystatechange = function() { + // if (xhr.readyState == 4) { + // ; + // } + // }; + + xhr.onload = function(event) { + var response = xhr.response; + var buf = new Uint8Array(response); + var headers = {}; + (xhr.getAllResponseHeaders() || '').replace( + /(?:\r?\n|^)([^:\r\n]+): *([^\r\n]+)/g, + function($0, $1, $2) { + headers[$1.toLowerCase()] = $2; + } + ); + return ret._success(buf, xhr.status, headers, options); + }; + + xhr.onerror = function(event) { + return ret._error(null, new Error(event.message), null, options); + }; + + if (options.data || options.body) { + xhr.send(options.data || options.body); + } else { + xhr.send(null); + } + + return ret; + } + + return ret; +}; + module.exports = require('soop')(Wallet); From 3b550d853fc9193c4a7f6538e6b753a776c66db0 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Fri, 1 Aug 2014 19:49:39 -0700 Subject: [PATCH 069/178] paypro: cleanup a lot of old code and comments. minor fixes. --- js/controllers/send.js | 4 ++-- js/directives.js | 12 +++--------- js/models/core/Wallet.js | 28 ++++------------------------ 3 files changed, 9 insertions(+), 35 deletions(-) diff --git a/js/controllers/send.js b/js/controllers/send.js index d210d368f..6831582f6 100644 --- a/js/controllers/send.js +++ b/js/controllers/send.js @@ -75,7 +75,7 @@ angular.module('copayApp.controllers').controller('SendController', + ' has been verified through ' + ca + '.'; } if (merchantData) { - message += '\nFor merchant: ' + merchantData.pr.payment_url; + message += '\nFor merchant: ' + merchantData.pr.pd.payment_url; } notification.success('Success!', message); $scope.loadTxs(); @@ -93,7 +93,7 @@ angular.module('copayApp.controllers').controller('SendController', + ' has been verified through ' + ca + '.'; } if (merchantData) { - message += '\nFor merchant: ' + merchantData.pr.payment_url; + message += '\nFor merchant: ' + merchantData.pr.pd.payment_url; } notification.success('Transaction broadcast', message); } else { diff --git a/js/directives.js b/js/directives.js index 7c09521ac..bfa9aa8f7 100644 --- a/js/directives.js +++ b/js/directives.js @@ -14,8 +14,6 @@ angular.module('copayApp.directives') var validator = function(value) { var uri = copay.HDPath.parseBitcoinURI(value); - window._rootScope = scope; - // Is this a payment protocol URI (BIP-72)? if (uri && uri.merchant) { scope.wallet.fetchPaymentTx(uri.merchant, function(err, merchantData) { @@ -46,9 +44,6 @@ angular.module('copayApp.directives') // XXX There needs to be a better way to do this: total = +total / config.unitToSatoshi; - var sendForm = angular.element( - document.getElementsByName('sendForm')[0]); - var address = angular.element( document.querySelector('input#address')); @@ -81,12 +76,10 @@ angular.module('copayApp.directives') sendall.attr('class', sendall.attr('class') + ' hidden'); // Reset all the changes from the payment protocol weirdness. - // XXX use ng-change attr instead //address.attr('ng-change', 'ppChange()'); //scope.ppChange = scope.ppChange || function() { //address.on('change', function(ev) { - scope.$on('change', function(ev) { - //scope.$watch('address', function(newValue, oldValue) { + scope.$watch('address', function(newValue, oldValue) { var val = address.val(); var uri = copay.HDPath.parseBitcoinURI(val || ''); if (!uri || !uri.merchant) { @@ -111,7 +104,6 @@ angular.module('copayApp.directives') // TODO: Check paymentRequest expiration, // delete if beyond expiration date. //}; - //}); }); //scope.$apply(); // scope.$digest(); @@ -120,9 +112,11 @@ angular.module('copayApp.directives') scope.sendForm.$valid = true; scope.sendForm.$invalid = false; scope.sendForm.$pristine = true; + scope.sendForm.address.$valid = true; scope.sendForm.address.$invalid = false; scope.sendForm.address.$pristine = true; + scope.sendForm.amount.$valid = true; scope.sendForm.amount.$invalid = false; scope.sendForm.amount.$pristine = true; diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index c1ceab8c9..a3ad9b40a 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -803,7 +803,7 @@ Wallet.prototype.createPaymentTx = function(options, cb) { 'Accept': PayPro.PAYMENT_REQUEST_CONTENT_TYPE + ', ' + PayPro.PAYMENT_ACK_CONTENT_TYPE, 'Content-Type': 'application/octet-stream' - // XHR does not allow these: + // XHR does not allow this: // 'Content-Length': 0 }, responseType: 'arraybuffer' @@ -856,7 +856,7 @@ Wallet.prototype.receivePaymentRequest = function(options, pr, cb) { var certs = PayPro.X509Certificates.decode(pki_data); certs = certs.certificate; - // XXX Temporary fix for tests + // Fix for older versions of bitcore if (!PayPro.RootCerts) { PayPro.RootCerts = { getTrusted: function() {} @@ -869,10 +869,6 @@ Wallet.prototype.receivePaymentRequest = function(options, pr, cb) { return PayPro.RootCerts.getTrusted(pem); }).filter(Boolean); - // if (!trusted.length) { - // return cb(new Error('Not a trusted certificate.')); - // } - // Verify Signature var verified = pr.verify(); @@ -1018,21 +1014,9 @@ Wallet.prototype.sendPaymentTx = function(ntxid, options, cb) { pay = pay.serialize(); - this.log(pay); + this.log('Sending Payment Message:'); this.log(pay.toString('hex')); - // https://www.google.com/search?q=angular+%24http+ArrayBuffer+in+body - // https://github.com/feross/buffer/blob/master/index.js - // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Typed_arrays - - // var view = new Uint8Array(new ArrayBuffer(pay.length)); - // Buffer._augment(view); - // pay = pay.copy(view); - - // var view = new Uint8Array(new ArrayBuffer(pay.length)); - // view.set(Array.prototype.slice.call(pay), 0); - // pay = view; - var buf = new ArrayBuffer(pay.length); var view = new Uint8Array(buf); for (var i = 0; i < pay.length; i++) { @@ -1051,11 +1035,7 @@ Wallet.prototype.sendPaymentTx = function(ntxid, options, cb) { // 'Content-Length': (pay.byteLength || pay.length) + '', // 'Content-Transfer-Encoding': 'binary' }, - // data: pay, - // data: pay, - // data: view, - data: buf, // Technically how this should be done. - // requestType: 'arraybuffer', + data: buf, // Technically how this should be done via XHR. responseType: 'arraybuffer' }) .success(function(data, status, headers, config) { From 4dd725aa481816bbde0d1d4600c458e0f00a24f5 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Fri, 1 Aug 2014 20:00:14 -0700 Subject: [PATCH 070/178] paypro: more cleaning up of form. --- js/controllers/send.js | 8 ++++++++ js/directives.js | 5 ++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/js/controllers/send.js b/js/controllers/send.js index 6831582f6..9753a76e3 100644 --- a/js/controllers/send.js +++ b/js/controllers/send.js @@ -65,6 +65,10 @@ angular.module('copayApp.controllers').controller('SendController', var txp = w.txProposals.txps[ntxid]; var merchantData = txp.merchant; var amt = angular.element(document.querySelector('input#amount')); + var sendto = angular.element(document + .querySelector('div.send-note > p[ng-class]:first-of-type')); + var tamount = angular.element(document + .querySelector('div.send-note > p[ng-class]:nth-of-type(2)')); var submit = angular.element(document.querySelector('button[type=submit]')); var sendall = angular.element(document.querySelector('[title="Send all funds"]')); if (w.isShared()) { @@ -81,6 +85,8 @@ angular.module('copayApp.controllers').controller('SendController', $scope.loadTxs(); if (merchantData) { amt.attr('disabled', false); + sendto.html(sendto.html().replace(/
Server:.*$/, '')); + tamount.html(''); submit.attr('disabled', true); sendall.attr('class', sendall.attr('class').replace(' hidden', '')); } @@ -103,6 +109,8 @@ angular.module('copayApp.controllers').controller('SendController', $scope.loadTxs(); if (merchantData) { amt.attr('disabled', false); + sendto.html(sendto.html().replace(/
Server:.*$/, '')); + tamount.html(''); submit.attr('disabled', true); sendall.attr('class', sendall.attr('class').replace(' hidden', '')); } diff --git a/js/directives.js b/js/directives.js index bfa9aa8f7..58c99db2e 100644 --- a/js/directives.js +++ b/js/directives.js @@ -86,13 +86,16 @@ angular.module('copayApp.directives') if (amount.attr('disabled') === true) { amount.attr('disabled', false); } - if (amount.attr('disabled') === false) { + if (submit.attr('disabled') === false) { submit.attr('disabled', true); } sendto.html(sendto.html().replace(/
Server:.*$/, '')); if (!/hidden/.test(tamount.attr('class'))) { tamount.attr(tamount.attr('class') + ' hidden'); } + if (~tamount.html().indexOf('(CA: ')) { + tamount.html(''); + } if (submit.attr('disabled') === false) { submit.attr('disabled', true); } From 64b38802bfcbeb1e17fdea13f71ff51215c1beaa Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Fri, 1 Aug 2014 20:22:11 -0700 Subject: [PATCH 071/178] paypro: fixes. clean up form reset from paypro. etc. --- js/controllers/send.js | 21 ------------ js/directives.js | 71 ++++++++++++++++++++-------------------- js/models/core/Wallet.js | 4 ++- 3 files changed, 38 insertions(+), 58 deletions(-) diff --git a/js/controllers/send.js b/js/controllers/send.js index 9753a76e3..f215a29f6 100644 --- a/js/controllers/send.js +++ b/js/controllers/send.js @@ -64,13 +64,6 @@ angular.module('copayApp.controllers').controller('SendController', function done(ntxid, ca) { var txp = w.txProposals.txps[ntxid]; var merchantData = txp.merchant; - var amt = angular.element(document.querySelector('input#amount')); - var sendto = angular.element(document - .querySelector('div.send-note > p[ng-class]:first-of-type')); - var tamount = angular.element(document - .querySelector('div.send-note > p[ng-class]:nth-of-type(2)')); - var submit = angular.element(document.querySelector('button[type=submit]')); - var sendall = angular.element(document.querySelector('[title="Send all funds"]')); if (w.isShared()) { $scope.loading = false; var message = 'The transaction proposal has been created'; @@ -83,13 +76,6 @@ angular.module('copayApp.controllers').controller('SendController', } notification.success('Success!', message); $scope.loadTxs(); - if (merchantData) { - amt.attr('disabled', false); - sendto.html(sendto.html().replace(/
Server:.*$/, '')); - tamount.html(''); - submit.attr('disabled', true); - sendall.attr('class', sendall.attr('class').replace(' hidden', '')); - } } else { w.sendTx(ntxid, function(txid, ca) { if (txid) { @@ -107,13 +93,6 @@ angular.module('copayApp.controllers').controller('SendController', } $scope.loading = false; $scope.loadTxs(); - if (merchantData) { - amt.attr('disabled', false); - sendto.html(sendto.html().replace(/
Server:.*$/, '')); - tamount.html(''); - submit.attr('disabled', true); - sendall.attr('class', sendall.attr('class').replace(' hidden', '')); - } }); } $rootScope.pendingPayment = null; diff --git a/js/directives.js b/js/directives.js index 58c99db2e..e1d009b44 100644 --- a/js/directives.js +++ b/js/directives.js @@ -44,11 +44,14 @@ angular.module('copayApp.directives') // XXX There needs to be a better way to do this: total = +total / config.unitToSatoshi; + // XXX Pretty much all of this code accesses the raw DOM. It's + // very bad, there's probably a better, more angular-y way to + // do things here. + var address = angular.element( document.querySelector('input#address')); - var amount = angular.element( - document.querySelector('input#amount')); + var amount = angular.element( document.querySelector('input#amount')); amount.val(total); amount.attr('disabled', true); @@ -67,8 +70,7 @@ angular.module('copayApp.directives') + expires.toISOString() + ')'); - var submit = angular.element( - document.querySelector('button[type=submit]')); + var submit = angular.element( document.querySelector('button[type=submit]')); submit.attr('disabled', false); var sendall = angular.element( @@ -76,39 +78,36 @@ angular.module('copayApp.directives') sendall.attr('class', sendall.attr('class') + ' hidden'); // Reset all the changes from the payment protocol weirdness. - //address.attr('ng-change', 'ppChange()'); - //scope.ppChange = scope.ppChange || function() { - //address.on('change', function(ev) { - scope.$watch('address', function(newValue, oldValue) { - var val = address.val(); - var uri = copay.HDPath.parseBitcoinURI(val || ''); - if (!uri || !uri.merchant) { - if (amount.attr('disabled') === true) { - amount.attr('disabled', false); + // XXX Bad hook. + if (!scope.__watchingAddress) { + scope.__watchingAddress = true; + scope.$watch('address', function(newValue, oldValue) { + var val = address.val(); + var uri = copay.HDPath.parseBitcoinURI(val || ''); + if (!uri || !uri.merchant) { + if (amount.attr('disabled')) { + amount.val(''); + amount.attr('disabled', false); + } + sendto.html(sendto.html().replace(/
Server:.*$/, '')); + if (!/hidden/.test(tamount.attr('class'))) { + tamount.attr(tamount.attr('class') + ' hidden'); + } + if (~tamount.html().indexOf('(CA: ')) { + tamount.html(''); + } + if (!submit.attr('disabled')) { + submit.attr('disabled', true); + } + if (/ hidden$/.test(sendall.attr('class'))) { + sendall.attr('class', + sendall.attr('class').replace(' hidden', '')); + } } - if (submit.attr('disabled') === false) { - submit.attr('disabled', true); - } - sendto.html(sendto.html().replace(/
Server:.*$/, '')); - if (!/hidden/.test(tamount.attr('class'))) { - tamount.attr(tamount.attr('class') + ' hidden'); - } - if (~tamount.html().indexOf('(CA: ')) { - tamount.html(''); - } - if (submit.attr('disabled') === false) { - submit.attr('disabled', true); - } - if (/ hidden$/.test(sendall.attr('class'))) { - sendall.attr('class', - sendall.attr('class').replace(' hidden', '')); - } - } - // TODO: Check paymentRequest expiration, - // delete if beyond expiration date. - //}; - }); - //scope.$apply(); // scope.$digest(); + // TODO: Check paymentRequest expiration, + // delete if beyond expiration date. + }); + } ctrl.$setValidity('validAddress', true); diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index a3ad9b40a..8e635cb56 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -1035,7 +1035,9 @@ Wallet.prototype.sendPaymentTx = function(ntxid, options, cb) { // 'Content-Length': (pay.byteLength || pay.length) + '', // 'Content-Transfer-Encoding': 'binary' }, - data: buf, // Technically how this should be done via XHR. + // Technically how this should be done via XHR (used to + // be the ArrayBuffer, now you send the View instead). + data: view, responseType: 'arraybuffer' }) .success(function(data, status, headers, config) { From a3714f8893450ddf3e17cf8a9a806a19e6413eff Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Fri, 1 Aug 2014 20:24:26 -0700 Subject: [PATCH 072/178] paypro: minor - split fix. style fix. --- js/controllers/send.js | 2 +- js/directives.js | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/js/controllers/send.js b/js/controllers/send.js index f215a29f6..a190ad269 100644 --- a/js/controllers/send.js +++ b/js/controllers/send.js @@ -102,7 +102,7 @@ angular.module('copayApp.controllers').controller('SendController', if (address.indexOf('bitcoin:') === 0) { uri = copay.HDPath.parseBitcoinURI(address); } else if (address.indexOf('Merchant: ') === 0) { - uri = { merchant: address.split(' ')[1] }; + uri = { merchant: address.split(/\s+/)[1] }; } if (uri && uri.merchant) { diff --git a/js/directives.js b/js/directives.js index e1d009b44..ec04e4195 100644 --- a/js/directives.js +++ b/js/directives.js @@ -51,7 +51,8 @@ angular.module('copayApp.directives') var address = angular.element( document.querySelector('input#address')); - var amount = angular.element( document.querySelector('input#amount')); + var amount = angular.element( + document.querySelector('input#amount')); amount.val(total); amount.attr('disabled', true); @@ -70,7 +71,8 @@ angular.module('copayApp.directives') + expires.toISOString() + ')'); - var submit = angular.element( document.querySelector('button[type=submit]')); + var submit = angular.element( + document.querySelector('button[type=submit]')); submit.attr('disabled', false); var sendall = angular.element( From bc4a8f73d833a4ebaef9b18bc9c63788e97cacda Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Fri, 1 Aug 2014 20:33:50 -0700 Subject: [PATCH 073/178] paypro: _resetPayPro to reset the form. --- js/directives.js | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/js/directives.js b/js/directives.js index ec04e4195..6859c5fa0 100644 --- a/js/directives.js +++ b/js/directives.js @@ -18,6 +18,7 @@ angular.module('copayApp.directives') if (uri && uri.merchant) { scope.wallet.fetchPaymentTx(uri.merchant, function(err, merchantData) { if (err) { + if (scope._resetPayPro) scope._resetPayPro(); ctrl.$setValidity('validAddress', false); return; } @@ -80,10 +81,10 @@ angular.module('copayApp.directives') sendall.attr('class', sendall.attr('class') + ' hidden'); // Reset all the changes from the payment protocol weirdness. - // XXX Bad hook. - if (!scope.__watchingAddress) { - scope.__watchingAddress = true; - scope.$watch('address', function(newValue, oldValue) { + // XXX Bad hook. Find a better more angular-y way of doing this. + // This will also closure scope every variable above forever. + if (!scope._resetPayPro) { + scope._resetPayPro = function() { var val = address.val(); var uri = copay.HDPath.parseBitcoinURI(val || ''); if (!uri || !uri.merchant) { @@ -108,11 +109,15 @@ angular.module('copayApp.directives') } // TODO: Check paymentRequest expiration, // delete if beyond expiration date. - }); + }; + scope.$watch('address',scope._resetPayPro); } ctrl.$setValidity('validAddress', true); + // XXX With PayPro, since amount is already filled in among + // other field oddities, the form is always invalid. Make it + // valid. scope.sendForm.$valid = true; scope.sendForm.$invalid = false; scope.sendForm.$pristine = true; From a93f4532c5a66abc518d1b471bffa74052060b7a Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Fri, 1 Aug 2014 20:51:54 -0700 Subject: [PATCH 074/178] paypro: fix paypro tests. --- test/mocks/FakePayProServer.js | 49 ++++++++++++++++++---------------- 1 file changed, 26 insertions(+), 23 deletions(-) diff --git a/test/mocks/FakePayProServer.js b/test/mocks/FakePayProServer.js index eaf611168..6ce42da22 100644 --- a/test/mocks/FakePayProServer.js +++ b/test/mocks/FakePayProServer.js @@ -136,26 +136,26 @@ function startServer(cb) { 169, // OP_HASH160 76, // OP_PUSHDATA1 20, // number of bytes - 0xcf, - 0xbe, - 0x41, - 0xf4, - 0xa5, - 0x18, - 0xed, - 0xc2, - 0x5a, - 0xf7, - 0x1b, - 0xaf, - 0xc7, - 0x2f, - 0xb6, - 0x1b, - 0xfc, - 0xfc, - 0x4f, - 0xcd, + 55, + 48, + 254, + 188, + 186, + 4, + 186, + 208, + 205, + 71, + 108, + 251, + 130, + 15, + 156, + 55, + 215, + 70, + 111, + 217, 136, // OP_EQUALVERIFY 172 // OP_CHECKSIG ])); @@ -181,7 +181,7 @@ function startServer(cb) { pd.set('network', 'test'); pd.set('outputs', outputs); pd.set('time', now); - pd.set('expires', now * 60 * 60 * 24); + pd.set('expires', now + 60 * 60 * 24); pd.set('memo', 'Hello, this is the server, we would like some money.'); var port = +req.headers.host.split(':')[1] || server.port; pd.set('payment_url', 'https://localhost:' + port + '/-/pay'); @@ -223,6 +223,9 @@ function startServer(cb) { '/-/pay': function(req, cb) { var body = req.body; + console.log('Received Payment Message Body:'); + console.log(body.toString('hex')); + var res = { statusCode: 200, headers: {}, @@ -238,7 +241,7 @@ function startServer(cb) { var refund_to = pay.get('refund_to'); var memo = pay.get('memo'); - console.log('Received payment from %s.', req.socket.remoteAddress); + console.log('Received Payment from %s.', req.socket.remoteAddress); console.log('Customer Message: %s', memo); console.log('Payment Message:'); console.log(pay); @@ -307,7 +310,7 @@ function startServer(cb) { var path = uri.replace(/^https?:\/\/[^\/]+/, ''); var req = options; req.headers = req.headers || {}; - req.body = req.body || {}; + req.body = req.data || req.body || {}; req.socket = { remoteAddress: 'localhost' }; From abf71a81ff064a9efe9e1dcc79cc568a84c23e54 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Mon, 4 Aug 2014 10:11:12 -0700 Subject: [PATCH 075/178] paypro: use an http proxy to handle self-signed certs - probably not a good idea. --- app.js | 79 ++++++++++++++++++-- js/models/core/Wallet.js | 154 +++++++++++++++++++++++++++------------ package.json | 3 +- 3 files changed, 181 insertions(+), 55 deletions(-) diff --git a/app.js b/app.js index 7c0f08a4d..ba4043c92 100644 --- a/app.js +++ b/app.js @@ -1,6 +1,7 @@ var express = require('express'); var http = require('http'); var app = express(); +var request = require('request'); app.use('/', express.static(__dirname + '/')); app.get('*', function(req, res) { @@ -12,29 +13,95 @@ app.start = function(port, callback) { app.set('port', port); app.use(express.static(__dirname)); + // XHR'ing from a site with a self-signed + // cert on the same port seems to work. if (process.env.USE_HTTPS) { - process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = '0'; var path = require('path'); + var bc = path.dirname(require.resolve('bitcore/package.json')); - // var fs = require('fs'); - // var server = require('https').createServer({ - // key: fs.readFileSync(bc + '/test/data/x509.key'), - // cert: fs.readFileSync(bc + '/test/data/x509.crt') - // }); var pserver = require(bc + '/examples/PayPro/server.js'); + pserver.removeListener('request', pserver.app); + pserver.on('request', function(req, res) { if (req.url.indexOf('/-/') === 0) { return pserver.app(req, res); } return app(req, res); }); + pserver.listen(port, function() { callback('https://localhost:' + port); }); + return; } + if (process.env.USE_REQUEST_PROXY) { + // Disable strict SSL + process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = '0'; + + // NOTE: Should be .use(), but the router is invoked + // to early above, which puts it on the stack. + // Only allow proxy requests from localhost + app.post('/_request', function(req, res, next) { + var address = req.socket.remoteAddress; + if (address !== '127.0.0.1' && address !== '::1') { + res.statusCode = 403; + res.end(); + return; + } + return next(); + }); + + // NOTE: Should be .use(), but the router is invoked + // to early above, which puts it on the stack. + app.post('/_request', function(req, res, next) { + var buf = ''; + + req.setEncoding('utf8'); + + req.on('error', function(err) { + try { + req.socket.destroy(); + } catch (e) { + ; + } + }); + + req.on('data', function(data) { + buf += data; + }); + + req.on('end', function(data) { + if (data) buf += data; + try { + req.reqOptions = JSON.parse(buf); + } catch (e) { + req.reqOptions = {}; + } + return next(); + }) + }); + + app.post('/_request', function(req, res, next) { + var options = req.reqOptions; + if (options.body) { + options.body = new Buffer(options.body, 'hex'); + } + request(options, function(err, response, body) { + if (err) { + res.statusCode = 500; + res.setHeader('Content-Type', 'text/plain; charset=utf-8'); + res.end(err.stack + ''); + return; + } + res.writeHead(response.statusCode, response.headers); + res.end(body); + }); + }); + } + app.listen(port, function() { callback('http://localhost:' + port); }); diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index 8e635cb56..9c556da99 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -1600,61 +1600,119 @@ G.$http = G.$http || function $http(options, callback) { var req = options; req.headers = req.headers || {}; - req.body = req.body || {}; + req.body = req.body || req.data || {}; - if (typeof XMLHttpRequest !== 'undefined') { - var xhr = new XMLHttpRequest(); - xhr.open(method, uri, true); + var xhr = new XMLHttpRequest(); + xhr.open(method, uri, true); - Object.keys(options.headers).forEach(function(key) { - var val = options.headers[key]; - if (key === 'Content-Length') return; - if (key === 'Content-Transfer-Encoding') return; - xhr.setRequestHeader(key, val); - }); + Object.keys(req.headers).forEach(function(key) { + var val = req.headers[key]; + if (key === 'Content-Length') return; + if (key === 'Content-Transfer-Encoding') return; + xhr.setRequestHeader(key, val); + }); - // For older browsers (binary data): - // xhr.overrideMimeType('text/plain; charset=x-user-defined'); + // For older browsers (binary data): + // xhr.overrideMimeType('text/plain; charset=x-user-defined'); - // Newer browsers (binary data): - // xhr.responseType = 'arraybuffer'; + // Newer browsers (binary data): + // xhr.responseType = 'arraybuffer'; - if (options.responseType) { - xhr.responseType = options.responseType; - } - - // xhr.onreadystatechange = function() { - // if (xhr.readyState == 4) { - // ; - // } - // }; - - xhr.onload = function(event) { - var response = xhr.response; - var buf = new Uint8Array(response); - var headers = {}; - (xhr.getAllResponseHeaders() || '').replace( - /(?:\r?\n|^)([^:\r\n]+): *([^\r\n]+)/g, - function($0, $1, $2) { - headers[$1.toLowerCase()] = $2; - } - ); - return ret._success(buf, xhr.status, headers, options); - }; - - xhr.onerror = function(event) { - return ret._error(null, new Error(event.message), null, options); - }; - - if (options.data || options.body) { - xhr.send(options.data || options.body); - } else { - xhr.send(null); - } - - return ret; + if (req.responseType) { + xhr.responseType = req.responseType; } + // xhr.onreadystatechange = function() { + // if (xhr.readyState == 4) { + // ; + // } + // }; + + xhr.onload = function(event) { + var response = xhr.response; + var buf = new Uint8Array(response); + var headers = {}; + (xhr.getAllResponseHeaders() || '').replace( + /(?:\r?\n|^)([^:\r\n]+): *([^\r\n]+)/g, + function($0, $1, $2) { + headers[$1.toLowerCase()] = $2; + } + ); + return ret._success(buf, xhr.status, headers, req); + }; + + xhr.onerror = function(event) { + return ret._error(null, new Error(event.message), null, req); + }; + + if (req.body) { + xhr.send(req.body); + } else { + xhr.send(null); + } + + return ret; + + return ret; +}; + +G.$http = G.$http || function $http(options, callback) { + if (typeof options === 'string') { + options = { uri: options }; + } + + var ret = { + success: function(cb) { + this._success = cb; + return this; + }, + error: function(cb) { + this._error = cb; + return this; + }, + _success: function() { + ; + }, + _error: function(_, err) { + throw err; + } + }; + + var xhr = new XMLHttpRequest(); + xhr.open('POST', '/_request', true); + xhr.setRequestHeader('Content-Type', 'application/json; charset=utf-8'); + xhr.responseType = 'arraybuffer'; + + xhr.onload = function(event) { + var response = xhr.response; + var buf = new Uint8Array(response); + var headers = {}; + (xhr.getAllResponseHeaders() || '').replace( + /(?:\r?\n|^)([^:\r\n]+): *([^\r\n]+)/g, + function($0, $1, $2) { + headers[$1.toLowerCase()] = $2; + } + ); + return ret._success(buf, xhr.status, headers, req); + }; + + xhr.onerror = function(event) { + return ret._error(null, new Error(event.message), null, req); + }; + + options.body = options.body || options.data; + + if (options.body) { + if (!Buffer.isBuffer(options.body)) { + options.body = new Buffer(options.body); + } + options.body = options.body.toString('hex'); + } + + options.encoding = null; + + xhr.send(JSON.stringify(options)); + return ret; }; diff --git a/package.json b/package.json index 6504fb200..8c53f6257 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,8 @@ "preconditions": "^1.0.7", "sinon": "1.9.1", "mocha-lcov-reporter": "0.0.1", - "mocha": "^1.18.2" + "mocha": "^1.18.2", + "request": "2.39.0" }, "scripts": { "shell": "node shell/scripts/launch.js", From 8786fd990580a081b09b53507e35d7ec2da6f3d9 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Mon, 4 Aug 2014 10:14:26 -0700 Subject: [PATCH 076/178] Revert "paypro: use an http proxy to handle self-signed certs - probably not a good idea." This reverts commit f8f192848dee35cffdfa79ee043941d0ea61a77e. --- app.js | 79 ++-------------------- js/models/core/Wallet.js | 142 ++++++++++++--------------------------- package.json | 3 +- 3 files changed, 49 insertions(+), 175 deletions(-) diff --git a/app.js b/app.js index ba4043c92..7c0f08a4d 100644 --- a/app.js +++ b/app.js @@ -1,7 +1,6 @@ var express = require('express'); var http = require('http'); var app = express(); -var request = require('request'); app.use('/', express.static(__dirname + '/')); app.get('*', function(req, res) { @@ -13,95 +12,29 @@ app.start = function(port, callback) { app.set('port', port); app.use(express.static(__dirname)); - // XHR'ing from a site with a self-signed - // cert on the same port seems to work. if (process.env.USE_HTTPS) { + process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = '0'; var path = require('path'); - var bc = path.dirname(require.resolve('bitcore/package.json')); + // var fs = require('fs'); + // var server = require('https').createServer({ + // key: fs.readFileSync(bc + '/test/data/x509.key'), + // cert: fs.readFileSync(bc + '/test/data/x509.crt') + // }); var pserver = require(bc + '/examples/PayPro/server.js'); - pserver.removeListener('request', pserver.app); - pserver.on('request', function(req, res) { if (req.url.indexOf('/-/') === 0) { return pserver.app(req, res); } return app(req, res); }); - pserver.listen(port, function() { callback('https://localhost:' + port); }); - return; } - if (process.env.USE_REQUEST_PROXY) { - // Disable strict SSL - process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = '0'; - - // NOTE: Should be .use(), but the router is invoked - // to early above, which puts it on the stack. - // Only allow proxy requests from localhost - app.post('/_request', function(req, res, next) { - var address = req.socket.remoteAddress; - if (address !== '127.0.0.1' && address !== '::1') { - res.statusCode = 403; - res.end(); - return; - } - return next(); - }); - - // NOTE: Should be .use(), but the router is invoked - // to early above, which puts it on the stack. - app.post('/_request', function(req, res, next) { - var buf = ''; - - req.setEncoding('utf8'); - - req.on('error', function(err) { - try { - req.socket.destroy(); - } catch (e) { - ; - } - }); - - req.on('data', function(data) { - buf += data; - }); - - req.on('end', function(data) { - if (data) buf += data; - try { - req.reqOptions = JSON.parse(buf); - } catch (e) { - req.reqOptions = {}; - } - return next(); - }) - }); - - app.post('/_request', function(req, res, next) { - var options = req.reqOptions; - if (options.body) { - options.body = new Buffer(options.body, 'hex'); - } - request(options, function(err, response, body) { - if (err) { - res.statusCode = 500; - res.setHeader('Content-Type', 'text/plain; charset=utf-8'); - res.end(err.stack + ''); - return; - } - res.writeHead(response.statusCode, response.headers); - res.end(body); - }); - }); - } - app.listen(port, function() { callback('http://localhost:' + port); }); diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index 9c556da99..8e635cb56 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -1600,119 +1600,61 @@ G.$http = G.$http || function $http(options, callback) { var req = options; req.headers = req.headers || {}; - req.body = req.body || req.data || {}; + req.body = req.body || {}; - var xhr = new XMLHttpRequest(); - xhr.open(method, uri, true); + if (typeof XMLHttpRequest !== 'undefined') { + var xhr = new XMLHttpRequest(); + xhr.open(method, uri, true); - Object.keys(req.headers).forEach(function(key) { - var val = req.headers[key]; - if (key === 'Content-Length') return; - if (key === 'Content-Transfer-Encoding') return; - xhr.setRequestHeader(key, val); - }); + Object.keys(options.headers).forEach(function(key) { + var val = options.headers[key]; + if (key === 'Content-Length') return; + if (key === 'Content-Transfer-Encoding') return; + xhr.setRequestHeader(key, val); + }); - // For older browsers (binary data): - // xhr.overrideMimeType('text/plain; charset=x-user-defined'); + // For older browsers (binary data): + // xhr.overrideMimeType('text/plain; charset=x-user-defined'); - // Newer browsers (binary data): - // xhr.responseType = 'arraybuffer'; + // Newer browsers (binary data): + // xhr.responseType = 'arraybuffer'; - if (req.responseType) { - xhr.responseType = req.responseType; - } - - // xhr.onreadystatechange = function() { - // if (xhr.readyState == 4) { - // ; - // } - // }; - - xhr.onload = function(event) { - var response = xhr.response; - var buf = new Uint8Array(response); - var headers = {}; - (xhr.getAllResponseHeaders() || '').replace( - /(?:\r?\n|^)([^:\r\n]+): *([^\r\n]+)/g, - function($0, $1, $2) { - headers[$1.toLowerCase()] = $2; - } - ); - return ret._success(buf, xhr.status, headers, req); - }; - - xhr.onerror = function(event) { - return ret._error(null, new Error(event.message), null, req); - }; - - if (req.body) { - xhr.send(req.body); - } else { - xhr.send(null); - } - - return ret; - - return ret; -}; - -G.$http = G.$http || function $http(options, callback) { - if (typeof options === 'string') { - options = { uri: options }; - } - - var ret = { - success: function(cb) { - this._success = cb; - return this; - }, - error: function(cb) { - this._error = cb; - return this; - }, - _success: function() { - ; - }, - _error: function(_, err) { - throw err; + if (options.responseType) { + xhr.responseType = options.responseType; } - }; - var xhr = new XMLHttpRequest(); - xhr.open('POST', '/_request', true); - xhr.setRequestHeader('Content-Type', 'application/json; charset=utf-8'); - xhr.responseType = 'arraybuffer'; + // xhr.onreadystatechange = function() { + // if (xhr.readyState == 4) { + // ; + // } + // }; - xhr.onload = function(event) { - var response = xhr.response; - var buf = new Uint8Array(response); - var headers = {}; - (xhr.getAllResponseHeaders() || '').replace( - /(?:\r?\n|^)([^:\r\n]+): *([^\r\n]+)/g, - function($0, $1, $2) { - headers[$1.toLowerCase()] = $2; - } - ); - return ret._success(buf, xhr.status, headers, req); - }; + xhr.onload = function(event) { + var response = xhr.response; + var buf = new Uint8Array(response); + var headers = {}; + (xhr.getAllResponseHeaders() || '').replace( + /(?:\r?\n|^)([^:\r\n]+): *([^\r\n]+)/g, + function($0, $1, $2) { + headers[$1.toLowerCase()] = $2; + } + ); + return ret._success(buf, xhr.status, headers, options); + }; - xhr.onerror = function(event) { - return ret._error(null, new Error(event.message), null, req); - }; + xhr.onerror = function(event) { + return ret._error(null, new Error(event.message), null, options); + }; - options.body = options.body || options.data; - - if (options.body) { - if (!Buffer.isBuffer(options.body)) { - options.body = new Buffer(options.body); + if (options.data || options.body) { + xhr.send(options.data || options.body); + } else { + xhr.send(null); } - options.body = options.body.toString('hex'); + + return ret; } - options.encoding = null; - - xhr.send(JSON.stringify(options)); - return ret; }; diff --git a/package.json b/package.json index 8c53f6257..6504fb200 100644 --- a/package.json +++ b/package.json @@ -14,8 +14,7 @@ "preconditions": "^1.0.7", "sinon": "1.9.1", "mocha-lcov-reporter": "0.0.1", - "mocha": "^1.18.2", - "request": "2.39.0" + "mocha": "^1.18.2" }, "scripts": { "shell": "node shell/scripts/launch.js", From 04432aa42635e0bfec0b992ccb1a01ef92f52147 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Mon, 4 Aug 2014 10:28:55 -0700 Subject: [PATCH 077/178] paypro: clean up makeshift $http module. --- js/models/core/Wallet.js | 81 ++++++++++++++++------------------------ 1 file changed, 33 insertions(+), 48 deletions(-) diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index 8e635cb56..a4d5544d7 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -1565,7 +1565,8 @@ Wallet.prototype.verifySignedJson = function(senderId, payload, signature) { // NOTE: Angular $http module does not send ArrayBuffers correctly, so we're // not going to use it. We'll have to write our own. Otherwise, we could // hex-encoded our messages and decode them on the other side, but that -// deviates from BIP-70 slightly. +// deviates from BIP-70. + // if (typeof angular !== 'undefined') { // G.$http = G.$http || angular.bootstrap().get('$http'); // } @@ -1600,59 +1601,43 @@ G.$http = G.$http || function $http(options, callback) { var req = options; req.headers = req.headers || {}; - req.body = req.body || {}; + req.body = req.body || req.data || {}; - if (typeof XMLHttpRequest !== 'undefined') { - var xhr = new XMLHttpRequest(); - xhr.open(method, uri, true); + var xhr = new XMLHttpRequest(); + xhr.open(method, uri, true); - Object.keys(options.headers).forEach(function(key) { - var val = options.headers[key]; - if (key === 'Content-Length') return; - if (key === 'Content-Transfer-Encoding') return; - xhr.setRequestHeader(key, val); - }); + Object.keys(req.headers).forEach(function(key) { + var val = req.headers[key]; + if (key === 'Content-Length') return; + if (key === 'Content-Transfer-Encoding') return; + xhr.setRequestHeader(key, val); + }); - // For older browsers (binary data): - // xhr.overrideMimeType('text/plain; charset=x-user-defined'); + if (req.responseType) { + xhr.responseType = req.responseType; + } - // Newer browsers (binary data): - // xhr.responseType = 'arraybuffer'; + xhr.onload = function(event) { + var response = xhr.response; + var buf = new Uint8Array(response); + var headers = {}; + (xhr.getAllResponseHeaders() || '').replace( + /(?:\r?\n|^)([^:\r\n]+): *([^\r\n]+)/g, + function($0, $1, $2) { + headers[$1.toLowerCase()] = $2; + } + ); + return ret._success(buf, xhr.status, headers, options); + }; - if (options.responseType) { - xhr.responseType = options.responseType; - } + xhr.onerror = function(event) { + return ret._error(null, new Error(event.message), null, options); + }; - // xhr.onreadystatechange = function() { - // if (xhr.readyState == 4) { - // ; - // } - // }; - - xhr.onload = function(event) { - var response = xhr.response; - var buf = new Uint8Array(response); - var headers = {}; - (xhr.getAllResponseHeaders() || '').replace( - /(?:\r?\n|^)([^:\r\n]+): *([^\r\n]+)/g, - function($0, $1, $2) { - headers[$1.toLowerCase()] = $2; - } - ); - return ret._success(buf, xhr.status, headers, options); - }; - - xhr.onerror = function(event) { - return ret._error(null, new Error(event.message), null, options); - }; - - if (options.data || options.body) { - xhr.send(options.data || options.body); - } else { - xhr.send(null); - } - - return ret; + if (req.body) { + xhr.send(req.body); + } else { + xhr.send(null); } return ret; From 87987fca2bc681dae61c93733e55c5cef8257f93 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Mon, 4 Aug 2014 10:41:03 -0700 Subject: [PATCH 078/178] paypro: cleanup refund_to. --- js/models/core/Wallet.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index a4d5544d7..98535ed0e 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -968,18 +968,20 @@ Wallet.prototype.sendPaymentTx = function(ntxid, options, cb) { || this.publicKeyRing.getPubKeys(0, false, this.getMyCopayerId())[0]; if (options.refund_to) { - // pubkey needs to be ripesha'd - options.refund_to = bitcore.util.sha256ripe160(options.refund_to); var total = txp.merchant.pr.pd.outputs.reduce(function(total, _, i) { return total.add(bignum.fromBuffer(tx.outs[i].v, { endian: 'little', size: 1 })); }, bignum('0', 10)); + var rpo = new PayPro(); rpo = rpo.makeOutput(); + // XXX Bad - the amount *has* to be a Number in protobufjs + // Possibly does not matter - server can ignore the amount anyway. rpo.set('amount', +total.toString(10)); + rpo.set('script', Buffer.concat([ new Buffer([ @@ -988,13 +990,15 @@ Wallet.prototype.sendPaymentTx = function(ntxid, options, cb) { 76, // OP_PUSHDATA1 20, // number of bytes ]), - options.refund_to, + // needs to be ripesha'd + bitcore.util.sha256ripe160(options.refund_to), new Buffer([ 136, // OP_EQUALVERIFY 172 // OP_CHECKSIG ]) ]) ); + refund_outputs.push(rpo.message); } From bfb7477f1a90f13606668f852bd457acab18e472 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Mon, 4 Aug 2014 10:41:35 -0700 Subject: [PATCH 079/178] paypro: add notification for payment ACK. --- js/controllers/send.js | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/js/controllers/send.js b/js/controllers/send.js index a190ad269..88c3101b7 100644 --- a/js/controllers/send.js +++ b/js/controllers/send.js @@ -332,11 +332,25 @@ angular.module('copayApp.controllers').controller('SendController', $scope.loading = true; $rootScope.txAlertCount = 0; var w = $rootScope.wallet; - w.sendTx(ntxid, function(txid) { + w.sendTx(ntxid, function(txid, ca) { if (!txid) { notification.error('Error', 'There was an error sending the transaction'); } else { - notification.success('Transaction broadcast', 'Transaction id: '+txid); + if (!ca) { + notification.success('Transaction broadcast', 'Transaction id: '+txid); + } else { + var txp = w.txProposals.txps[ntxid]; + var merchantData = txp.merchant; + var message = 'Transaction ID: ' + txid; + if (ca) { + message += '\nThis payment protocol transaction' + + ' has been verified through ' + ca + '.'; + } + if (merchantData) { + message += '\nFor merchant: ' + merchantData.pr.pd.payment_url; + } + notification.success('Transaction sent', message); + } } if (cb) return cb(); From c940cb25b5d086a2e4d29f2ab1bf69bb8e7f35ba Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Mon, 4 Aug 2014 10:47:04 -0700 Subject: [PATCH 080/178] paypro: return merchantData in createTx and sendTx. --- js/controllers/send.js | 36 +++++++++++++++--------------------- js/models/core/Wallet.js | 4 ++-- 2 files changed, 17 insertions(+), 23 deletions(-) diff --git a/js/controllers/send.js b/js/controllers/send.js index 88c3101b7..5af53273a 100644 --- a/js/controllers/send.js +++ b/js/controllers/send.js @@ -61,30 +61,28 @@ angular.module('copayApp.controllers').controller('SendController', var w = $rootScope.wallet; - function done(ntxid, ca) { - var txp = w.txProposals.txps[ntxid]; - var merchantData = txp.merchant; + function done(ntxid, merchantData) { if (w.isShared()) { $scope.loading = false; var message = 'The transaction proposal has been created'; - if (ca) { - message += '\nThis payment protocol transaction' - + ' has been verified through ' + ca + '.'; - } if (merchantData) { + if (merchantData.pr.ca) { + message += '\nThis payment protocol transaction' + + ' has been verified through ' + merchantData.pr.ca + '.'; + } message += '\nFor merchant: ' + merchantData.pr.pd.payment_url; } notification.success('Success!', message); $scope.loadTxs(); } else { - w.sendTx(ntxid, function(txid, ca) { + w.sendTx(ntxid, function(txid, merchantData) { if (txid) { var message = 'Transaction id: ' + txid; - if (ca) { - message += '\nThis payment protocol transaction' - + ' has been verified through ' + ca + '.'; - } if (merchantData) { + if (merchantData.pr.ca) { + message += '\nThis payment protocol transaction' + + ' has been verified through ' + merchantData.pr.ca + '.'; + } message += '\nFor merchant: ' + merchantData.pr.pd.payment_url; } notification.success('Transaction broadcast', message); @@ -332,23 +330,19 @@ angular.module('copayApp.controllers').controller('SendController', $scope.loading = true; $rootScope.txAlertCount = 0; var w = $rootScope.wallet; - w.sendTx(ntxid, function(txid, ca) { + w.sendTx(ntxid, function(txid, merchantData) { if (!txid) { notification.error('Error', 'There was an error sending the transaction'); } else { - if (!ca) { + if (!merchantData) { notification.success('Transaction broadcast', 'Transaction id: '+txid); } else { - var txp = w.txProposals.txps[ntxid]; - var merchantData = txp.merchant; var message = 'Transaction ID: ' + txid; - if (ca) { + if (merchantData.pr.ca) { message += '\nThis payment protocol transaction' - + ' has been verified through ' + ca + '.'; - } - if (merchantData) { - message += '\nFor merchant: ' + merchantData.pr.pd.payment_url; + + ' has been verified through ' + merchantData.pr.ca + '.'; } + message += '\nFor merchant: ' + merchantData.pr.pd.payment_url; notification.success('Transaction sent', message); } } diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index 98535ed0e..79801267c 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -943,7 +943,7 @@ Wallet.prototype.receivePaymentRequest = function(options, pr, cb) { self.log('The server sent you a message:'); self.log(memo); - return cb(ntxid, ca); + return cb(ntxid, merchantData); }); }; @@ -1083,7 +1083,7 @@ Wallet.prototype.receivePaymentRequestACK = function(tx, txp, ack, cb) { } var txid = tx.getHash().toString('hex'); - return cb(txid, txp.merchant.pr.ca); + return cb(txid, txp.merchant); }; Wallet.prototype.createPaymentTxSync = function(options, merchantData, unspent) { From fb77e46111cad0f7a2a830d630a38a6d3479d2b8 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Mon, 4 Aug 2014 10:59:35 -0700 Subject: [PATCH 081/178] paypro: cleanup app.js. --- app.js | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/app.js b/app.js index 7c0f08a4d..d531b0274 100644 --- a/app.js +++ b/app.js @@ -13,25 +13,24 @@ app.start = function(port, callback) { app.use(express.static(__dirname)); if (process.env.USE_HTTPS) { - process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = '0'; var path = require('path'); + var bc = path.dirname(require.resolve('bitcore/package.json')); - // var fs = require('fs'); - // var server = require('https').createServer({ - // key: fs.readFileSync(bc + '/test/data/x509.key'), - // cert: fs.readFileSync(bc + '/test/data/x509.crt') - // }); var pserver = require(bc + '/examples/PayPro/server.js'); + pserver.removeListener('request', pserver.app); + pserver.on('request', function(req, res) { if (req.url.indexOf('/-/') === 0) { return pserver.app(req, res); } return app(req, res); }); + pserver.listen(port, function() { callback('https://localhost:' + port); }); + return; } From e1478fefbfe810dba63e5cfb70afee37209b179f Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Mon, 4 Aug 2014 12:17:40 -0700 Subject: [PATCH 082/178] paypro: display ack memo on notification. --- js/controllers/send.js | 3 +++ js/models/core/Wallet.js | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/js/controllers/send.js b/js/controllers/send.js index 5af53273a..86e8831f6 100644 --- a/js/controllers/send.js +++ b/js/controllers/send.js @@ -70,6 +70,7 @@ angular.module('copayApp.controllers').controller('SendController', message += '\nThis payment protocol transaction' + ' has been verified through ' + merchantData.pr.ca + '.'; } + message += '\nMessage from server: ' + merchantData.ack.memo; message += '\nFor merchant: ' + merchantData.pr.pd.payment_url; } notification.success('Success!', message); @@ -83,6 +84,7 @@ angular.module('copayApp.controllers').controller('SendController', message += '\nThis payment protocol transaction' + ' has been verified through ' + merchantData.pr.ca + '.'; } + message += '\nMessage from server: ' + merchantData.ack.memo; message += '\nFor merchant: ' + merchantData.pr.pd.payment_url; } notification.success('Transaction broadcast', message); @@ -342,6 +344,7 @@ angular.module('copayApp.controllers').controller('SendController', message += '\nThis payment protocol transaction' + ' has been verified through ' + merchantData.pr.ca + '.'; } + message += '\nMessage from server: ' + merchantData.ack.memo; message += '\nFor merchant: ' + merchantData.pr.pd.payment_url; notification.success('Transaction sent', message); } diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index 79801267c..462dbd014 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -1082,6 +1082,10 @@ Wallet.prototype.receivePaymentRequestACK = function(tx, txp, ack, cb) { tx = ptx; } + txp.merchant.ack = { + memo: memo + }; + var txid = tx.getHash().toString('hex'); return cb(txid, txp.merchant); }; From 6d64935889932a14efec25bc44da90feb6904c14 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Mon, 4 Aug 2014 15:55:46 -0700 Subject: [PATCH 083/178] paypro: begin refactoring tests. --- test/test.PayPro.js | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/test/test.PayPro.js b/test/test.PayPro.js index fcb3605dd..a31b9d2e1 100644 --- a/test/test.PayPro.js +++ b/test/test.PayPro.js @@ -137,6 +137,18 @@ describe('PayPro (in Wallet) model', function() { 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, []); + }, 1); + }; + return w; + }; + it('#start the example server', function(done) { startServer(function(err, s) { if (err) return done(err); @@ -147,15 +159,8 @@ describe('PayPro (in Wallet) model', function() { }); it('#send a payment request', function(done) { - var w = cachedCreateW2(); + var w = createWallet(); should.exist(w); - 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, []); - }, 1); - }; var address = 'bitcoin:mq7se9wy2egettFxPbmn99cK8v5AFq55Lx?amount=0.11&r=' + server.uri + '/request'; var commentText = 'Hello, server. I\'d like to make a payment.'; w.createTx(address, commentText, function(ntxid, ca) { From f67e30bfd23f53912904e8d9f267f3291a497b56 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Mon, 4 Aug 2014 16:07:54 -0700 Subject: [PATCH 084/178] paypro: start writing more tests. --- test/test.PayPro.js | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/test/test.PayPro.js b/test/test.PayPro.js index a31b9d2e1..242a936bd 100644 --- a/test/test.PayPro.js +++ b/test/test.PayPro.js @@ -158,6 +158,24 @@ describe('PayPro (in Wallet) model', function() { }); }); + it('#retrieve a payment request message via http', function(done) { + var w = createWallet(); + should.exist(w); + var req = {}; + server.POST['/-/request'](req, function(err, res, body) { + done(); + }); + }); + + it('#send a payment message via http', function(done) { + var w = createWallet(); + should.exist(w); + var req = {}; + server.POST['/-/pay'](req, function(err, res, body) { + done(); + }); + }); + it('#send a payment request', function(done) { var w = createWallet(); should.exist(w); From 785a9cdd315151832566620215a4105c20f55e3e Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Mon, 4 Aug 2014 16:43:45 -0700 Subject: [PATCH 085/178] paypro: tests - send raw http requests to test mock server. --- js/models/core/Wallet.js | 2 +- test/test.PayPro.js | 211 +++++++++++++++++++++++++++++++++++++-- 2 files changed, 206 insertions(+), 7 deletions(-) diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index 462dbd014..5f026dc01 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -804,7 +804,7 @@ Wallet.prototype.createPaymentTx = function(options, cb) { + ', ' + PayPro.PAYMENT_ACK_CONTENT_TYPE, 'Content-Type': 'application/octet-stream' // XHR does not allow this: - // 'Content-Length': 0 + // 'Content-Length': '0' }, responseType: 'arraybuffer' }) diff --git a/test/test.PayPro.js b/test/test.PayPro.js index 242a936bd..07bc8c654 100644 --- a/test/test.PayPro.js +++ b/test/test.PayPro.js @@ -158,11 +158,31 @@ describe('PayPro (in Wallet) model', function() { }); }); + var pr; + it('#retrieve a payment request message via http', function(done) { var w = createWallet(); should.exist(w); - var req = {}; + + 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: {} + }; + server.POST['/-/request'](req, function(err, res, body) { + var data = PayPro.PaymentRequest.decode(body); + pr = new PayPro(); + pr = pr.makePaymentRequest(data); done(); }); }); @@ -170,8 +190,187 @@ describe('PayPro (in Wallet) model', function() { it('#send a payment message via http', function(done) { var w = createWallet(); should.exist(w); - var req = {}; + + 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 opts = { + remainderOut: { + address: w._doGenerateAddress(true).toString() + } + }; + + var outs = []; + outputs.forEach(function(output) { + outs.push({ + address: w.getAddressesStr()[0] || '2N6J45pqfu5y7zgWDwXDAmdd8qzK1oRdz3A', + amountSatStr: '0' + }); + }); + + var b = new bitcore.TransactionBuilder(opts) + .setUnspent(unspent) + .setOutputs(outs); + + 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); + } + + outputs.forEach(function(output, i) { + var amount = output.get('amount'); + var script = { + offset: output.get('script').offset, + limit: output.get('script').limit, + buffer: output.get('script').buffer + }; + + var v = new Buffer(8); + v[0] = (amount.low >> 0) & 0xff; + v[1] = (amount.low >> 8) & 0xff; + v[2] = (amount.low >> 16) & 0xff; + v[3] = (amount.low >> 24) & 0xff; + v[4] = (amount.high >> 0) & 0xff; + v[5] = (amount.high >> 8) & 0xff; + v[6] = (amount.high >> 16) & 0xff; + v[7] = (amount.high >> 24) & 0xff; + + var s = script.buffer.slice(script.offset, script.limit); + + b.tx.outs[i].v = v; + b.tx.outs[i].s = s; + }); + + 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) { + return total.add(bitcore.Bignum.fromBuffer(tx.outs[i].v, { + endian: 'little', + 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(options.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 + }; + 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) { + return total.add(bitcore.Bignum.fromBuffer(tx.outs[i].v, { + endian: 'little', + size: 1 + })); + }, bitcore.Bignum('0', 10)); + + assert.equal(ackTotal.toString(10), total.toString(10)); + done(); }); }); @@ -181,21 +380,21 @@ describe('PayPro (in Wallet) model', function() { should.exist(w); var address = 'bitcoin:mq7se9wy2egettFxPbmn99cK8v5AFq55Lx?amount=0.11&r=' + server.uri + '/request'; var commentText = 'Hello, server. I\'d like to make a payment.'; - w.createTx(address, commentText, function(ntxid, ca) { + w.createTx(address, commentText, function(ntxid, merchantData) { if (w.totalCopayers > 1) { should.exist(ntxid); console.log('Sent TX proposal to other copayers:'); - console.log([ntxid, ca]); + console.log([ntxid, merchantData]); server.close(function() { done(); }); } else { console.log('Sending TX to merchant server:'); console.log(ntxid); - w.sendTx(ntxid, function(txid, ca) { + w.sendTx(ntxid, function(txid, merchantData) { should.exist(txid); console.log('TX sent:'); - console.log([ntxid, ca]); + console.log([ntxid, merchantData]); server.close(function() { done(); }); From 9c4ee94e355e2d49c561ed1048b0d3ffd0283ee1 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Mon, 4 Aug 2014 16:52:10 -0700 Subject: [PATCH 086/178] paypro: 4 tests passing. --- test/test.PayPro.js | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/test/test.PayPro.js b/test/test.PayPro.js index 07bc8c654..17f95247b 100644 --- a/test/test.PayPro.js +++ b/test/test.PayPro.js @@ -179,6 +179,10 @@ describe('PayPro (in Wallet) model', function() { body: {} }; + Object.keys(req.headers).forEach(function(key) { + req.headers[key.toLowerCase()] = req.headers[key]; + }); + server.POST['/-/request'](req, function(err, res, body) { var data = PayPro.PaymentRequest.decode(body); pr = new PayPro(); @@ -218,6 +222,9 @@ describe('PayPro (in Wallet) model', function() { 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() @@ -233,7 +240,7 @@ describe('PayPro (in Wallet) model', function() { }); var b = new bitcore.TransactionBuilder(opts) - .setUnspent(unspent) + .setUnspent(unspentTest) .setOutputs(outs); var selectedUtxos = b.getSelectedUnspent(); @@ -299,7 +306,7 @@ describe('PayPro (in Wallet) model', function() { 20, // number of bytes ]), // needs to be ripesha'd - bitcore.util.sha256ripe160(options.refund_to), + bitcore.util.sha256ripe160(refund_to), new Buffer([ 136, // OP_EQUALVERIFY 172 // OP_CHECKSIG @@ -334,6 +341,10 @@ describe('PayPro (in Wallet) model', function() { 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); @@ -369,7 +380,7 @@ describe('PayPro (in Wallet) model', function() { })); }, bitcore.Bignum('0', 10)); - assert.equal(ackTotal.toString(10), total.toString(10)); + ackTotal.toString(10).should.equal(total.toString(10)); done(); }); From 919681633a855fc0700aeadd21959c481f5d9020 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Mon, 4 Aug 2014 17:26:43 -0700 Subject: [PATCH 087/178] paypro: 9 tests passing. --- test/test.PayPro.js | 283 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 281 insertions(+), 2 deletions(-) diff --git a/test/test.PayPro.js b/test/test.PayPro.js index 17f95247b..ad31492e9 100644 --- a/test/test.PayPro.js +++ b/test/test.PayPro.js @@ -159,9 +159,12 @@ describe('PayPro (in Wallet) model', function() { }); var pr; + var ppw; + + ppw = createWallet(); it('#retrieve a payment request message via http', function(done) { - var w = createWallet(); + var w = ppw; should.exist(w); var req = { @@ -192,7 +195,7 @@ describe('PayPro (in Wallet) model', function() { }); it('#send a payment message via http', function(done) { - var w = createWallet(); + var w = ppw; should.exist(w); var ver = pr.get('payment_details_version'); @@ -386,6 +389,282 @@ describe('PayPro (in Wallet) model', function() { }); }); + 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.POST['/-/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) { + outs.push({ + address: w.getAddressesStr()[0] || '2N6J45pqfu5y7zgWDwXDAmdd8qzK1oRdz3A', + amountSatStr: '0' + }); + }); + + var b = new bitcore.TransactionBuilder(opts) + .setUnspent(unspentTest) + .setOutputs(outs); + + 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); + } + + outputs.forEach(function(output, i) { + var amount = output.get('amount'); + var script = { + offset: output.get('script').offset, + limit: output.get('script').limit, + buffer: output.get('script').buffer + }; + + var v = new Buffer(8); + v[0] = (amount.low >> 0) & 0xff; + v[1] = (amount.low >> 8) & 0xff; + v[2] = (amount.low >> 16) & 0xff; + v[3] = (amount.low >> 24) & 0xff; + v[4] = (amount.high >> 0) & 0xff; + v[5] = (amount.high >> 8) & 0xff; + v[6] = (amount.high >> 16) & 0xff; + v[7] = (amount.high >> 24) & 0xff; + + var s = script.buffer.slice(script.offset, script.limit); + + b.tx.outs[i].v = v; + b.tx.outs[i].s = s; + }); + + 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) { + return total.add(bitcore.Bignum.fromBuffer(tx.outs[i].v, { + endian: 'little', + 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) { + return total.add(bitcore.Bignum.fromBuffer(tx.outs[i].v, { + endian: 'little', + 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) { + if (!ntxid) { + return done(new Error('No TX proposal.')); + } + 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) { + if (!txid) { + return done(new Error('No TX ID.')); + } + should.exist(merchantData.ack); + merchantData.ack.memo.should.equal('Thank you for your payment!'); + return done(); + }); + }); + it('#send a payment request', function(done) { var w = createWallet(); should.exist(w); From 31719e62dd6e3257090131f19923159a05bd59b7 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Mon, 4 Aug 2014 17:37:37 -0700 Subject: [PATCH 088/178] paypro: 12 tests passing. --- test/test.PayPro.js | 80 ++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 71 insertions(+), 9 deletions(-) diff --git a/test/test.PayPro.js b/test/test.PayPro.js index ad31492e9..0ce3f58f5 100644 --- a/test/test.PayPro.js +++ b/test/test.PayPro.js @@ -665,19 +665,53 @@ describe('PayPro (in Wallet) model', function() { }); }); - it('#send a payment request', function(done) { + it('#send a payment request using payment api', function(done) { var w = createWallet(); should.exist(w); - var address = 'bitcoin:mq7se9wy2egettFxPbmn99cK8v5AFq55Lx?amount=0.11&r=' + server.uri + '/request'; - var commentText = 'Hello, server. I\'d like to make a payment.'; - w.createTx(address, commentText, function(ntxid, merchantData) { + 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) { if (w.totalCopayers > 1) { should.exist(ntxid); console.log('Sent TX proposal to other copayers:'); console.log([ntxid, merchantData]); - server.close(function() { - done(); + return done(); + } else { + console.log('Sending TX to merchant server:'); + console.log(ntxid); + w.sendPaymentTx(ntxid, { memo: memo }, function(txid, merchantData) { + should.exist(txid); + console.log('TX sent:'); + console.log([ntxid, 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 = copay.HDPath.parseBitcoinURI(address); + } else if (address.indexOf('Merchant: ') === 0) { + uri = address.split(/\s+/)[1]; + } + + w.createTx(uri, commentText, function(ntxid, merchantData) { + if (w.totalCopayers > 1) { + should.exist(ntxid); + console.log('Sent TX proposal to other copayers:'); + console.log([ntxid, merchantData]); + return done(); } else { console.log('Sending TX to merchant server:'); console.log(ntxid); @@ -685,11 +719,39 @@ describe('PayPro (in Wallet) model', function() { should.exist(txid); console.log('TX sent:'); console.log([ntxid, merchantData]); - server.close(function() { - done(); - }); + 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.totalCopayers > 1) { + should.exist(ntxid); + console.log('Sent TX proposal to other copayers:'); + console.log([ntxid, merchantData]); + return done(); + } else { + console.log('Sending TX to merchant server:'); + console.log(ntxid); + w.sendTx(ntxid, function(txid, merchantData) { + should.exist(txid); + console.log('TX sent:'); + console.log([ntxid, merchantData]); + return done(); + }); + } + }); + }); + + it('#close payment server', function(done) { + server.close(function() { + return done(); + }); + }); }); From 4d2879c5b68408fb2239a08ece0092bee8f8ec26 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Tue, 5 Aug 2014 10:11:59 -0700 Subject: [PATCH 089/178] paypro: expose serialized payment request data on tx proposal. --- js/models/core/Wallet.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index 5f026dc01..774771bc3 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -921,7 +921,10 @@ Wallet.prototype.receivePaymentRequest = function(options, pr, cb) { untrusted: !ca }, request_url: options.uri, - total: bignum('0', 10).toString(10) + total: bignum('0', 10).toString(10), + // Expose so other copayers can verify signature + // and identity, not to mention data. + raw: pr.serialize().toString('hex') }; return this.getUnspent(function(err, unspent) { From 0d4db05bf68bdc5893b901d4a8ad18138a1b5c74 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Tue, 5 Aug 2014 10:50:09 -0700 Subject: [PATCH 090/178] paypro: add verifyPaymentRequest so we do not have to trust other copayers. --- js/models/core/Wallet.js | 86 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index 774771bc3..4ae8a01e4 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -1200,6 +1200,92 @@ Wallet.prototype.createPaymentTxSync = function(options, merchantData, unspent) return ntxid; }; +Wallet.prototype.verifyPaymentRequest = function(ntxid) { + if (!txp) return false; + + var txp = typeof ntxid !== 'object' + ? this.txProposals.txps[ntxid] + : ntxid; + + // If we're not a payment protocol proposal, ignore. + if (!txp.merchant) return true; + + // The copayer didn't send us the raw payment request, unverifiable. + if (!txp.merchant.raw) return false; + + // var tx = txp.builder.tx; + var tx = txp.builder.build(); + + var data = new Buffer(txp.merchant.raw, 'hex'); + data = PayPro.PaymentRequest.decode(data); + var pr = new PayPro(); + pr = pr.makePaymentRequest(data); + + // Verify the signature so we know this is the real request. + if (!pr.verify()) { + // Signature does not match cert. It may have + // been modified by an untrustworthy person. + // We should not sign this transaction proposal! + return false; + } + + 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'); + + for (var i = 0; i < outputs.length; i++) { + var output = outputs[i]; + + 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 v = new Buffer(8); + v[0] = (amount.low >> 0) & 0xff; + v[1] = (amount.low >> 8) & 0xff; + v[2] = (amount.low >> 16) & 0xff; + v[3] = (amount.low >> 24) & 0xff; + v[4] = (amount.high >> 0) & 0xff; + v[5] = (amount.high >> 8) & 0xff; + v[6] = (amount.high >> 16) & 0xff; + v[7] = (amount.high >> 24) & 0xff; + + // Expected value + var ev = bignum.fromBuffer(v, { + endian: 'little', + size: 1 + }); + + // Expected script + var es = script.buffer.slice(script.offset, script.limit); + + // Actual value + var av = bignum.fromBuffer(tx.outs[i].v, { + endian: 'little', + size: 1 + }); + + // Actual script + var as = tx.outs[i].s; + + // Make sure the tx's output script and values match the payment request's. + if (av.toString(10) !== ev.toString(10) + || as.toString('hex') !== es.toString('hex')) { + // Verifiable outputs do not match outputs of merchant + // data. We should not sign this transaction proposal! + return false; + } + } + + return true; +}; + Wallet.prototype.addSeenToTxProposals = function() { var ret = false; var myId = this.getMyCopayerId(); From 6ccbf292c59f63c83f605a9f7709d975db7dd80c Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Tue, 5 Aug 2014 11:07:16 -0700 Subject: [PATCH 091/178] paypro: start verifying raw payment requests before signing. --- js/models/core/Wallet.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index 4ae8a01e4..f8793e623 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -718,6 +718,12 @@ Wallet.prototype.sign = function(ntxid, cb) { // if (cb) cb(false); // } // + + if (!self.verifyPaymentRequest(ntxid)) { + if (cb) cb(false); + return; + } + var keys = self.privateKey.getForPaths(txp.inputChainPaths); var b = txp.builder; From 60603306a9bf7222a994329dbef418cc510213e7 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Tue, 5 Aug 2014 11:10:31 -0700 Subject: [PATCH 092/178] paypro: comments. --- js/models/core/Wallet.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index f8793e623..d727b6889 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -719,6 +719,8 @@ Wallet.prototype.sign = function(ntxid, cb) { // } // + // If this is a payment protocol request, + // ensure it hasn't been tampered with. if (!self.verifyPaymentRequest(ntxid)) { if (cb) cb(false); return; @@ -1206,6 +1208,10 @@ Wallet.prototype.createPaymentTxSync = function(options, merchantData, unspent) return ntxid; }; +// This essentially ensures that a copayer hasn't tampered with a +// PaymentRequest message from a payment server. It verifies the signature +// based on the cert, and checks to ensure the desired outputs are the same as +// the ones on the tx proposal. Wallet.prototype.verifyPaymentRequest = function(ntxid) { if (!txp) return false; From 37d1562a3f113ab015846be8deaaa241ec68c490 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Tue, 5 Aug 2014 11:13:11 -0700 Subject: [PATCH 093/178] paypro: optimize verifyPaymentRequest check. --- js/models/core/Wallet.js | 35 ++++++++++++++++------------------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index d727b6889..bea1103b8 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -1248,6 +1248,11 @@ Wallet.prototype.verifyPaymentRequest = function(ntxid) { var outputs = pd.get('outputs'); + if (tx.outs.length < outputs.length) { + // Outputs do not and cannot match. + return false; + } + for (var i = 0; i < outputs.length; i++) { var output = outputs[i]; @@ -1258,36 +1263,28 @@ Wallet.prototype.verifyPaymentRequest = function(ntxid) { buffer: new Buffer(new Uint8Array(output.get('script').buffer)) }; - var v = new Buffer(8); - v[0] = (amount.low >> 0) & 0xff; - v[1] = (amount.low >> 8) & 0xff; - v[2] = (amount.low >> 16) & 0xff; - v[3] = (amount.low >> 24) & 0xff; - v[4] = (amount.high >> 0) & 0xff; - v[5] = (amount.high >> 8) & 0xff; - v[6] = (amount.high >> 16) & 0xff; - v[7] = (amount.high >> 24) & 0xff; - // Expected value - var ev = bignum.fromBuffer(v, { - endian: 'little', - size: 1 - }); + var ev = new Buffer(8); + ev[0] = (amount.low >> 0) & 0xff; + ev[1] = (amount.low >> 8) & 0xff; + ev[2] = (amount.low >> 16) & 0xff; + ev[3] = (amount.low >> 24) & 0xff; + ev[4] = (amount.high >> 0) & 0xff; + ev[5] = (amount.high >> 8) & 0xff; + ev[6] = (amount.high >> 16) & 0xff; + ev[7] = (amount.high >> 24) & 0xff; // Expected script var es = script.buffer.slice(script.offset, script.limit); // Actual value - var av = bignum.fromBuffer(tx.outs[i].v, { - endian: 'little', - size: 1 - }); + var av = tx.outs[i].v; // Actual script var as = tx.outs[i].s; // Make sure the tx's output script and values match the payment request's. - if (av.toString(10) !== ev.toString(10) + if (av.toString('hex') !== ev.toString('hex') || as.toString('hex') !== es.toString('hex')) { // Verifiable outputs do not match outputs of merchant // data. We should not sign this transaction proposal! From 3ff4eb26ea988fbfc4e7f5386166afe5e56ec737 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Tue, 5 Aug 2014 12:35:17 -0700 Subject: [PATCH 094/178] paypro: add tests for verifyPaymentRequest and signing tampered txs/prs. --- test/test.PayPro.js | 86 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) diff --git a/test/test.PayPro.js b/test/test.PayPro.js index 0ce3f58f5..004ab60d9 100644 --- a/test/test.PayPro.js +++ b/test/test.PayPro.js @@ -749,6 +749,92 @@ describe('PayPro (in Wallet) model', function() { }); }); + 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); + + console.log('Sending TX to merchant server:'); + console.log(ntxid); + + // 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.txps[ntxid]; + should.exist(txp); + should.exist(txp.signedBy[myId]); + should.not.exist(txp.rejectedBy[myId]); + delete txp.signedBy[myId]; + + w.verifyPaymentRequest(ntxid).should.equal(false); + + w.sign(ntxid, function(signed) { + should.exist(signed); + signed.should.equal(false); + console.log('TX not signed.'); + 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); + + console.log('Sending TX to merchant server:'); + console.log(ntxid); + + // 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; + + // Tamper with payment request in its abstract form: + var txp = w.txProposals.txps[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.txps[ntxid]; + should.exist(txp); + should.exist(txp.signedBy[myId]); + should.not.exist(txp.rejectedBy[myId]); + delete txp.signedBy[myId]; + + w.verifyPaymentRequest(ntxid).should.equal(false); + + w.sign(ntxid, function(signed) { + should.exist(signed); + signed.should.equal(false); + console.log('TX not signed.'); + return done(); + }); + }); + }); + it('#close payment server', function(done) { server.close(function() { return done(); From a0137b9e5502284afd2182f3eae79990635b7a2b Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Tue, 5 Aug 2014 12:43:52 -0700 Subject: [PATCH 095/178] paypro: sign an untampered payment request in tests. --- test/test.PayPro.js | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/test/test.PayPro.js b/test/test.PayPro.js index 004ab60d9..80bac03e9 100644 --- a/test/test.PayPro.js +++ b/test/test.PayPro.js @@ -835,6 +835,39 @@ describe('PayPro (in Wallet) model', function() { }); }); + 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); + + console.log('Sending TX to merchant server:'); + console.log(ntxid); + + var myId = w.getMyCopayerId(); + var txp = w.txProposals.txps[ntxid]; + should.exist(txp); + should.exist(txp.signedBy[myId]); + should.not.exist(txp.rejectedBy[myId]); + delete txp.signedBy[myId]; + + w.verifyPaymentRequest(ntxid).should.equal(true); + + // w.sign(ntxid, function(signed) { + // should.exist(signed); + // // signed.should.equal(true); // false for some reason + // console.log('TX signed successfully.'); + // return done(); + // }); + + console.log('PR verfied successfully.'); + return done(); + }); + }); + it('#close payment server', function(done) { server.close(function() { return done(); From d0f09745679c75f73ddaa4e4e29ae64ddb89f78e Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Tue, 5 Aug 2014 12:46:57 -0700 Subject: [PATCH 096/178] paypro: fix verifyPaymentRequest. passing tests. --- js/models/core/Wallet.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index bea1103b8..7835fc3ce 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -1213,7 +1213,7 @@ Wallet.prototype.createPaymentTxSync = function(options, merchantData, unspent) // based on the cert, and checks to ensure the desired outputs are the same as // the ones on the tx proposal. Wallet.prototype.verifyPaymentRequest = function(ntxid) { - if (!txp) return false; + if (!ntxid) return false; var txp = typeof ntxid !== 'object' ? this.txProposals.txps[ntxid] From 21196f8b22cb07b7e9252195c1ea48f198374461 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Tue, 5 Aug 2014 12:49:50 -0700 Subject: [PATCH 097/178] paypro: not necessary, but check merchant data in verifyPaymentRequest. --- js/models/core/Wallet.js | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index 7835fc3ce..97182fe20 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -1290,6 +1290,31 @@ Wallet.prototype.verifyPaymentRequest = function(ntxid) { // data. We should not sign this transaction proposal! return false; } + + // Checking the merchant data itself isn't technically + // necessary as long as we check the transaction, but + // we can do it for good measure. + var ro = txp.merchant.pr.pd.outputs[i]; + + // Actual value + var av = new Buffer(8); + av[0] = (ro.amount.low >> 0) & 0xff; + av[1] = (ro.amount.low >> 8) & 0xff; + av[2] = (ro.amount.low >> 16) & 0xff; + av[3] = (ro.amount.low >> 24) & 0xff; + av[4] = (ro.amount.high >> 0) & 0xff; + av[5] = (ro.amount.high >> 8) & 0xff; + av[6] = (ro.amount.high >> 16) & 0xff; + av[7] = (ro.amount.high >> 24) & 0xff; + + // Actual script + var as = new Buffer(ro.script.buffer, 'hex') + .slice(ro.script.offset, ro.script.limit); + + if (av.toString('hex') !== ev.toString('hex') + || as.toString('hex') !== es.toString('hex')) { + return false; + } } return true; From a942df63df08ce62ac3e382fc941ee43450afcf2 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Tue, 5 Aug 2014 12:51:28 -0700 Subject: [PATCH 098/178] paypro: drop sign() calls in verify tests. --- test/test.PayPro.js | 26 ++++---------------------- 1 file changed, 4 insertions(+), 22 deletions(-) diff --git a/test/test.PayPro.js b/test/test.PayPro.js index 80bac03e9..85f51a3a9 100644 --- a/test/test.PayPro.js +++ b/test/test.PayPro.js @@ -781,16 +781,11 @@ describe('PayPro (in Wallet) model', function() { should.exist(txp); should.exist(txp.signedBy[myId]); should.not.exist(txp.rejectedBy[myId]); - delete txp.signedBy[myId]; w.verifyPaymentRequest(ntxid).should.equal(false); - w.sign(ntxid, function(signed) { - should.exist(signed); - signed.should.equal(false); - console.log('TX not signed.'); - return done(); - }); + console.log('TX not verified.'); + return done(); }); }); @@ -822,16 +817,11 @@ describe('PayPro (in Wallet) model', function() { should.exist(txp); should.exist(txp.signedBy[myId]); should.not.exist(txp.rejectedBy[myId]); - delete txp.signedBy[myId]; w.verifyPaymentRequest(ntxid).should.equal(false); - w.sign(ntxid, function(signed) { - should.exist(signed); - signed.should.equal(false); - console.log('TX not signed.'); - return done(); - }); + console.log('TX not verified.'); + return done(); }); }); @@ -852,17 +842,9 @@ describe('PayPro (in Wallet) model', function() { should.exist(txp); should.exist(txp.signedBy[myId]); should.not.exist(txp.rejectedBy[myId]); - delete txp.signedBy[myId]; w.verifyPaymentRequest(ntxid).should.equal(true); - // w.sign(ntxid, function(signed) { - // should.exist(signed); - // // signed.should.equal(true); // false for some reason - // console.log('TX signed successfully.'); - // return done(); - // }); - console.log('PR verfied successfully.'); return done(); }); From 65058051960c520f7a64819e2db03d575e325119 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Tue, 5 Aug 2014 12:54:05 -0700 Subject: [PATCH 099/178] paypro: add separate tests for verifying abstract txp tx and pr. --- test/test.PayPro.js | 33 +++++++++++++++++++++++++++++---- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/test/test.PayPro.js b/test/test.PayPro.js index 85f51a3a9..bd00d643e 100644 --- a/test/test.PayPro.js +++ b/test/test.PayPro.js @@ -802,10 +802,35 @@ describe('PayPro (in Wallet) model', function() { console.log(ntxid); // 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 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.txps[ntxid]; + should.exist(txp); + should.exist(txp.signedBy[myId]); + should.not.exist(txp.rejectedBy[myId]); + + w.verifyPaymentRequest(ntxid).should.equal(false); + + console.log('TX not verified.'); + 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); + + console.log('Sending TX to merchant server:'); + console.log(ntxid); // Tamper with payment request in its abstract form: var txp = w.txProposals.txps[ntxid]; From 696323d496a6edf77d76b651e43e8ef2408af64c Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Tue, 5 Aug 2014 12:56:18 -0700 Subject: [PATCH 100/178] paypro: tests - add more should() calls. --- test/test.PayPro.js | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/test/test.PayPro.js b/test/test.PayPro.js index bd00d643e..ac6757560 100644 --- a/test/test.PayPro.js +++ b/test/test.PayPro.js @@ -643,9 +643,8 @@ describe('PayPro (in Wallet) model', function() { should.exist(req); delete w.paymentRequests[options.uri]; w.receivePaymentRequest(options, req.pr, function(ntxid, merchantData) { - if (!ntxid) { - return done(new Error('No TX proposal.')); - } + should.exist(ntxid); + should.exist(merchantData); w._ntxid = ntxid; merchantData.pr.pd.payment_url.should.equal('https://localhost:8080/-/pay'); return done(); @@ -656,9 +655,8 @@ describe('PayPro (in Wallet) model', function() { var w = ppw; should.exist(w); w.sendPaymentTx(w._ntxid, function(txid, merchantData) { - if (!txid) { - return done(new Error('No TX ID.')); - } + should.exist(txid); + should.exist(merchantData); should.exist(merchantData.ack); merchantData.ack.memo.should.equal('Thank you for your payment!'); return done(); @@ -674,8 +672,9 @@ describe('PayPro (in Wallet) model', function() { uri: uri, memo: memo }, function(ntxid, merchantData) { + should.exist(ntxid); + should.exist(merchantData); if (w.totalCopayers > 1) { - should.exist(ntxid); console.log('Sent TX proposal to other copayers:'); console.log([ntxid, merchantData]); return done(); @@ -684,6 +683,7 @@ describe('PayPro (in Wallet) model', function() { console.log(ntxid); w.sendPaymentTx(ntxid, { memo: memo }, function(txid, merchantData) { should.exist(txid); + should.exist(merchantData); console.log('TX sent:'); console.log([ntxid, merchantData]); return done(); @@ -709,14 +709,17 @@ describe('PayPro (in Wallet) model', function() { w.createTx(uri, commentText, function(ntxid, merchantData) { if (w.totalCopayers > 1) { should.exist(ntxid); + should.exist(merchantData); console.log('Sent TX proposal to other copayers:'); console.log([ntxid, merchantData]); return done(); } else { console.log('Sending TX to merchant server:'); console.log(ntxid); + should.exist(merchantData); w.sendTx(ntxid, function(txid, merchantData) { should.exist(txid); + should.exist(merchantData); console.log('TX sent:'); console.log([ntxid, merchantData]); return done(); @@ -733,6 +736,7 @@ describe('PayPro (in Wallet) model', function() { w.createTx(address, commentText, function(ntxid, merchantData) { if (w.totalCopayers > 1) { should.exist(ntxid); + should.exist(merchantData); console.log('Sent TX proposal to other copayers:'); console.log([ntxid, merchantData]); return done(); @@ -741,6 +745,7 @@ describe('PayPro (in Wallet) model', function() { console.log(ntxid); w.sendTx(ntxid, function(txid, merchantData) { should.exist(txid); + should.exist(merchantData); console.log('TX sent:'); console.log([ntxid, merchantData]); return done(); From d635c87935b0aa058a8dffaa5bd2bd5b06dff792 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Tue, 5 Aug 2014 12:59:44 -0700 Subject: [PATCH 101/178] paypro: turn sig into hex string on txp. --- js/models/core/Wallet.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index 97182fe20..8ff94303e 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -924,7 +924,7 @@ Wallet.prototype.receivePaymentRequest = function(options, pr, cb) { payment_url: payment_url, merchant_data: merchant_data.toString('hex') }, - signature: sig, + signature: sig.toString('hex'), ca: ca, untrusted: !ca }, From 8cbc231a0628f84099f0e1970344cc7006880acd Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Tue, 5 Aug 2014 13:03:04 -0700 Subject: [PATCH 102/178] paypro: remove logs. see #1043. --- js/models/core/Wallet.js | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index 8ff94303e..db8d63159 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -949,11 +949,6 @@ Wallet.prototype.receivePaymentRequest = function(options, pr, cb) { self.emit('txProposalsUpdated'); } - self.log('You are currently on this BTC network:'); - self.log(network); - self.log('The server sent you a message:'); - self.log(memo); - return cb(ntxid, merchantData); }); }; @@ -1029,9 +1024,6 @@ Wallet.prototype.sendPaymentTx = function(ntxid, options, cb) { pay = pay.serialize(); - this.log('Sending Payment Message:'); - this.log(pay.toString('hex')); - var buf = new ArrayBuffer(pay.length); var view = new Uint8Array(buf); for (var i = 0; i < pay.length; i++) { @@ -1072,9 +1064,6 @@ Wallet.prototype.receivePaymentRequestACK = function(tx, txp, ack, cb) { var payment = ack.get('payment'); var memo = ack.get('memo'); - this.log('Our payment was acknowledged!'); - this.log('Message from Merchant: %s', memo); - payment = PayPro.Payment.decode(payment); var pay = new PayPro(); payment = pay.makePayment(payment); @@ -1177,11 +1166,6 @@ Wallet.prototype.createPaymentTxSync = function(options, merchantData, unspent) if (options.fetch) return; - this.log(''); - this.log('Created transaction:'); - this.log(b.tx.getStandardizedObject()); - this.log(''); - var myId = this.getMyCopayerId(); var now = Date.now(); From 0a8d734e49ccf9f785c366f01fac59f2ad779ab2 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Tue, 5 Aug 2014 14:36:38 -0700 Subject: [PATCH 103/178] paypro: deal with no unspent outputs. see #1043. --- js/directives.js | 6 ++++++ js/models/core/Wallet.js | 3 +++ 2 files changed, 9 insertions(+) diff --git a/js/directives.js b/js/directives.js index 6859c5fa0..3cc79625d 100644 --- a/js/directives.js +++ b/js/directives.js @@ -17,6 +17,12 @@ angular.module('copayApp.directives') // Is this a payment protocol URI (BIP-72)? if (uri && uri.merchant) { scope.wallet.fetchPaymentTx(uri.merchant, function(err, merchantData) { + if ((err && err.message === 'No unspent outputs.') + || scope.availableBalance < +merchantData.total) { + ctrl.$setValidity('validAddress', false); + return; + } + if (err) { if (scope._resetPayPro) scope._resetPayPro(); ctrl.$setValidity('validAddress', false); diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index db8d63159..122192f0f 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -937,6 +937,9 @@ Wallet.prototype.receivePaymentRequest = function(options, pr, cb) { return this.getUnspent(function(err, unspent) { if (options.fetch) { + if (!unspent || !unspent.length) { + return cb(new Error('No unspent outputs.')); + } self.createPaymentTxSync(options, merchantData, unspent); return cb(null, merchantData, pr); } From bd24f51da40e1e0c296cacf9cd493a46da54adf5 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Tue, 5 Aug 2014 15:14:31 -0700 Subject: [PATCH 104/178] put logs back in wallet model for payment protocol This reverts commit e79d86d90625375e22ac2cbafb2c1f894a450bcf. --- js/models/core/Wallet.js | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index 122192f0f..3b4ab64f6 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -952,6 +952,11 @@ Wallet.prototype.receivePaymentRequest = function(options, pr, cb) { self.emit('txProposalsUpdated'); } + self.log('You are currently on this BTC network:'); + self.log(network); + self.log('The server sent you a message:'); + self.log(memo); + return cb(ntxid, merchantData); }); }; @@ -1027,6 +1032,9 @@ Wallet.prototype.sendPaymentTx = function(ntxid, options, cb) { pay = pay.serialize(); + this.log('Sending Payment Message:'); + this.log(pay.toString('hex')); + var buf = new ArrayBuffer(pay.length); var view = new Uint8Array(buf); for (var i = 0; i < pay.length; i++) { @@ -1067,6 +1075,9 @@ Wallet.prototype.receivePaymentRequestACK = function(tx, txp, ack, cb) { var payment = ack.get('payment'); var memo = ack.get('memo'); + this.log('Our payment was acknowledged!'); + this.log('Message from Merchant: %s', memo); + payment = PayPro.Payment.decode(payment); var pay = new PayPro(); payment = pay.makePayment(payment); @@ -1169,6 +1180,11 @@ Wallet.prototype.createPaymentTxSync = function(options, merchantData, unspent) if (options.fetch) return; + this.log(''); + this.log('Created transaction:'); + this.log(b.tx.getStandardizedObject()); + this.log(''); + var myId = this.getMyCopayerId(); var now = Date.now(); From 5f20b09b0b91e5c9c95305a92663d7732e3f839a Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Tue, 5 Aug 2014 15:15:38 -0700 Subject: [PATCH 105/178] paypro: remove logs from tests. see #1043. --- test/test.PayPro.js | 34 ---------------------------------- 1 file changed, 34 deletions(-) diff --git a/test/test.PayPro.js b/test/test.PayPro.js index ac6757560..f8149e990 100644 --- a/test/test.PayPro.js +++ b/test/test.PayPro.js @@ -675,17 +675,11 @@ describe('PayPro (in Wallet) model', function() { should.exist(ntxid); should.exist(merchantData); if (w.totalCopayers > 1) { - console.log('Sent TX proposal to other copayers:'); - console.log([ntxid, merchantData]); return done(); } else { - console.log('Sending TX to merchant server:'); - console.log(ntxid); w.sendPaymentTx(ntxid, { memo: memo }, function(txid, merchantData) { should.exist(txid); should.exist(merchantData); - console.log('TX sent:'); - console.log([ntxid, merchantData]); return done(); }); } @@ -710,18 +704,12 @@ describe('PayPro (in Wallet) model', function() { if (w.totalCopayers > 1) { should.exist(ntxid); should.exist(merchantData); - console.log('Sent TX proposal to other copayers:'); - console.log([ntxid, merchantData]); return done(); } else { - console.log('Sending TX to merchant server:'); - console.log(ntxid); should.exist(merchantData); w.sendTx(ntxid, function(txid, merchantData) { should.exist(txid); should.exist(merchantData); - console.log('TX sent:'); - console.log([ntxid, merchantData]); return done(); }); } @@ -737,17 +725,11 @@ describe('PayPro (in Wallet) model', function() { if (w.totalCopayers > 1) { should.exist(ntxid); should.exist(merchantData); - console.log('Sent TX proposal to other copayers:'); - console.log([ntxid, merchantData]); return done(); } else { - console.log('Sending TX to merchant server:'); - console.log(ntxid); w.sendTx(ntxid, function(txid, merchantData) { should.exist(txid); should.exist(merchantData); - console.log('TX sent:'); - console.log([ntxid, merchantData]); return done(); }); } @@ -763,9 +745,6 @@ describe('PayPro (in Wallet) model', function() { should.exist(ntxid); should.exist(merchantData); - console.log('Sending TX to merchant server:'); - console.log(ntxid); - // Tamper with payment request in its raw form: var data = new Buffer(merchantData.raw, 'hex'); data = PayPro.PaymentRequest.decode(data); @@ -789,7 +768,6 @@ describe('PayPro (in Wallet) model', function() { w.verifyPaymentRequest(ntxid).should.equal(false); - console.log('TX not verified.'); return done(); }); }); @@ -803,9 +781,6 @@ describe('PayPro (in Wallet) model', function() { should.exist(ntxid); should.exist(merchantData); - console.log('Sending TX to merchant server:'); - console.log(ntxid); - // Tamper with payment request in its abstract form: var outputs = merchantData.pr.pd.outputs; var output = outputs[outputs.length - 1]; @@ -820,7 +795,6 @@ describe('PayPro (in Wallet) model', function() { w.verifyPaymentRequest(ntxid).should.equal(false); - console.log('TX not verified.'); return done(); }); }); @@ -834,9 +808,6 @@ describe('PayPro (in Wallet) model', function() { should.exist(ntxid); should.exist(merchantData); - console.log('Sending TX to merchant server:'); - console.log(ntxid); - // Tamper with payment request in its abstract form: var txp = w.txProposals.txps[ntxid]; var tx = txp.builder.tx || txp.builder.build(); @@ -850,7 +821,6 @@ describe('PayPro (in Wallet) model', function() { w.verifyPaymentRequest(ntxid).should.equal(false); - console.log('TX not verified.'); return done(); }); }); @@ -864,9 +834,6 @@ describe('PayPro (in Wallet) model', function() { should.exist(ntxid); should.exist(merchantData); - console.log('Sending TX to merchant server:'); - console.log(ntxid); - var myId = w.getMyCopayerId(); var txp = w.txProposals.txps[ntxid]; should.exist(txp); @@ -875,7 +842,6 @@ describe('PayPro (in Wallet) model', function() { w.verifyPaymentRequest(ntxid).should.equal(true); - console.log('PR verfied successfully.'); return done(); }); }); From 18d1e4f6e75cc7d8e8d371a8993ee4dbc5064865 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Tue, 5 Aug 2014 16:14:50 -0700 Subject: [PATCH 106/178] paypro: reorganize createPaymentTxSync. --- js/models/core/Wallet.js | 24 ++++++++++---------- test/test.PayPro.js | 48 ++++++++++++++++++++-------------------- 2 files changed, 36 insertions(+), 36 deletions(-) diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index 3b4ab64f6..c0f14c11a 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -1133,18 +1133,6 @@ Wallet.prototype.createPaymentTxSync = function(options, merchantData, unspent) .setUnspent(unspent) .setOutputs(outs); - 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); - } - merchantData.total = bignum(merchantData.total, 10); merchantData.pr.pd.outputs.forEach(function(output, i) { @@ -1178,6 +1166,18 @@ Wallet.prototype.createPaymentTxSync = function(options, merchantData, unspent) merchantData.total = merchantData.total.toString(10); + 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); + } + if (options.fetch) return; this.log(''); diff --git a/test/test.PayPro.js b/test/test.PayPro.js index f8149e990..b78a484eb 100644 --- a/test/test.PayPro.js +++ b/test/test.PayPro.js @@ -246,18 +246,6 @@ describe('PayPro (in Wallet) model', function() { .setUnspent(unspentTest) .setOutputs(outs); - 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); - } - outputs.forEach(function(output, i) { var amount = output.get('amount'); var script = { @@ -282,6 +270,18 @@ describe('PayPro (in Wallet) model', function() { 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 = []; @@ -472,18 +472,6 @@ describe('PayPro (in Wallet) model', function() { .setUnspent(unspentTest) .setOutputs(outs); - 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); - } - outputs.forEach(function(output, i) { var amount = output.get('amount'); var script = { @@ -508,6 +496,18 @@ describe('PayPro (in Wallet) model', function() { 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 = []; From 093fd68c784e280533aa76c23cac3b9c8311f517 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Tue, 5 Aug 2014 17:01:17 -0700 Subject: [PATCH 107/178] paypro: extract addresses from server outputs and craft txs in this manner. --- js/models/core/Wallet.js | 55 +++++++++++++++++++++++++++++- test/test.PayPro.js | 72 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 126 insertions(+), 1 deletion(-) diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index c0f14c11a..b472189df 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -1120,6 +1120,48 @@ Wallet.prototype.createPaymentTxSync = function(options, merchantData, unspent) } }; + merchantData.total = bignum(merchantData.total, 10); + + var outs = []; + merchantData.pr.pd.outputs.forEach(function(output) { + var amount = output.amount; + + 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 script = { + offset: output.script.offset, + limit: output.script.limit, + buffer: new Buffer(output.script.buffer, 'hex') + }; + var s = script.buffer.slice(script.offset, script.limit); + var network = merchantData.pr.pd.network === 'main' ? 'livenet' : 'testnet'; + var addr = bitcore.Address.fromScriptPubKey(new bitcore.Script(s), network); + + outs.push({ + address: addr[0].toString(), + amountSatStr: bignum.fromBuffer(v, { + endian: 'little', + size: 1 + }).toString(10) + }); + + merchantData.total = merchantData.total.add(bignum.fromBuffer(v, { + endian: 'little', + size: 1 + })); + }); + + merchantData.total = merchantData.total.toString(10); + +/* var outs = []; merchantData.pr.pd.outputs.forEach(function(output) { outs.push({ @@ -1128,11 +1170,13 @@ Wallet.prototype.createPaymentTxSync = function(options, merchantData, unspent) amountSatStr: '0' // dummy amount }); }); +*/ var b = new Builder(opts) .setUnspent(unspent) .setOutputs(outs); +/* merchantData.total = bignum(merchantData.total, 10); merchantData.pr.pd.outputs.forEach(function(output, i) { @@ -1165,6 +1209,7 @@ Wallet.prototype.createPaymentTxSync = function(options, merchantData, unspent) }); merchantData.total = merchantData.total.toString(10); +*/ var selectedUtxos = b.getSelectedUnspent(); var inputChainPaths = selectedUtxos.map(function(utxo) { @@ -1284,7 +1329,15 @@ Wallet.prototype.verifyPaymentRequest = function(ntxid) { var av = tx.outs[i].v; // Actual script - var as = tx.outs[i].s; + // var as = tx.outs[i].s; + + // XXX allow changing of script as long as address is same + var as = es; + + // XXX allow changing of script as long as address is same + // var network = pd.get('network') === 'main' ? 'livenet' : 'testnet'; + // var es = bitcore.Address.fromScriptPubKey(new bitcore.Script(es), network)[0]; + // var as = bitcore.Address.fromScriptPubKey(new bitcore.Script(tx.outs[i].s), network)[0]; // Make sure the tx's output script and values match the payment request's. if (av.toString('hex') !== ev.toString('hex') diff --git a/test/test.PayPro.js b/test/test.PayPro.js index b78a484eb..e3555a749 100644 --- a/test/test.PayPro.js +++ b/test/test.PayPro.js @@ -234,6 +234,39 @@ describe('PayPro (in Wallet) model', function() { } }; + var outs = []; + outputs.forEach(function(output) { + var amount = output.get('amount'); + var script = { + offset: output.get('script').offset, + limit: output.get('script').limit, + buffer: output.get('script').buffer + }; + + 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 network = network === 'main' ? 'livenet' : 'testnet'; + var addr = bitcore.Address.fromScriptPubKey(new bitcore.Script(s), network); + + outs.push({ + address: addr[0].toString(), + amountSatStr: bitcore.Bignum.fromBuffer(v, { + endian: 'little', + size: 1 + }).toString(10) + }); + }); + +/* var outs = []; outputs.forEach(function(output) { outs.push({ @@ -241,11 +274,13 @@ describe('PayPro (in Wallet) model', function() { amountSatStr: '0' }); }); +*/ var b = new bitcore.TransactionBuilder(opts) .setUnspent(unspentTest) .setOutputs(outs); +/* outputs.forEach(function(output, i) { var amount = output.get('amount'); var script = { @@ -269,6 +304,7 @@ describe('PayPro (in Wallet) model', function() { b.tx.outs[i].v = v; b.tx.outs[i].s = s; }); +*/ var selectedUtxos = b.getSelectedUnspent(); var inputChainPaths = selectedUtxos.map(function(utxo) { @@ -460,6 +496,39 @@ describe('PayPro (in Wallet) model', function() { } }; + var outs = []; + outputs.forEach(function(output) { + var amount = output.get('amount'); + var script = { + offset: output.get('script').offset, + limit: output.get('script').limit, + buffer: output.get('script').buffer + }; + + 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 network = network === 'main' ? 'livenet' : 'testnet'; + var addr = bitcore.Address.fromScriptPubKey(new bitcore.Script(s), network); + + outs.push({ + address: addr[0].toString(), + amountSatStr: bitcore.Bignum.fromBuffer(v, { + endian: 'little', + size: 1 + }).toString(10) + }); + }); + +/* var outs = []; outputs.forEach(function(output) { outs.push({ @@ -467,11 +536,13 @@ describe('PayPro (in Wallet) model', function() { amountSatStr: '0' }); }); +*/ var b = new bitcore.TransactionBuilder(opts) .setUnspent(unspentTest) .setOutputs(outs); +/* outputs.forEach(function(output, i) { var amount = output.get('amount'); var script = { @@ -495,6 +566,7 @@ describe('PayPro (in Wallet) model', function() { b.tx.outs[i].v = v; b.tx.outs[i].s = s; }); +*/ var selectedUtxos = b.getSelectedUnspent(); var inputChainPaths = selectedUtxos.map(function(utxo) { From ac32f56afa2aa8fb4cc67a8822971e4540b1184b Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Tue, 5 Aug 2014 17:02:14 -0700 Subject: [PATCH 108/178] paypro: drop old tx code. --- js/models/core/Wallet.js | 46 ------------------------- test/test.PayPro.js | 72 ---------------------------------------- 2 files changed, 118 deletions(-) diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index b472189df..73fb086a6 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -1161,56 +1161,10 @@ Wallet.prototype.createPaymentTxSync = function(options, merchantData, unspent) merchantData.total = merchantData.total.toString(10); -/* - var outs = []; - merchantData.pr.pd.outputs.forEach(function(output) { - outs.push({ - address: self.getAddressesStr()[0] - || '2N6J45pqfu5y7zgWDwXDAmdd8qzK1oRdz3A', // dummy address - amountSatStr: '0' // dummy amount - }); - }); -*/ - var b = new Builder(opts) .setUnspent(unspent) .setOutputs(outs); -/* - merchantData.total = bignum(merchantData.total, 10); - - merchantData.pr.pd.outputs.forEach(function(output, i) { - var amount = output.amount; - var script = { - offset: output.script.offset, - limit: output.script.limit, - buffer: new Buffer(output.script.buffer, 'hex') - }; - - var v = new Buffer(8); - v[0] = (amount.low >> 0) & 0xff; - v[1] = (amount.low >> 8) & 0xff; - v[2] = (amount.low >> 16) & 0xff; - v[3] = (amount.low >> 24) & 0xff; - v[4] = (amount.high >> 0) & 0xff; - v[5] = (amount.high >> 8) & 0xff; - v[6] = (amount.high >> 16) & 0xff; - v[7] = (amount.high >> 24) & 0xff; - - var s = script.buffer.slice(script.offset, script.limit); - - b.tx.outs[i].v = v; - b.tx.outs[i].s = s; - - merchantData.total = merchantData.total.add(bignum.fromBuffer(v, { - endian: 'little', - size: 1 - })); - }); - - merchantData.total = merchantData.total.toString(10); -*/ - var selectedUtxos = b.getSelectedUnspent(); var inputChainPaths = selectedUtxos.map(function(utxo) { return pkr.pathForAddress(utxo.address); diff --git a/test/test.PayPro.js b/test/test.PayPro.js index e3555a749..a5e9186cb 100644 --- a/test/test.PayPro.js +++ b/test/test.PayPro.js @@ -266,46 +266,10 @@ describe('PayPro (in Wallet) model', function() { }); }); -/* - var outs = []; - outputs.forEach(function(output) { - outs.push({ - address: w.getAddressesStr()[0] || '2N6J45pqfu5y7zgWDwXDAmdd8qzK1oRdz3A', - amountSatStr: '0' - }); - }); -*/ - var b = new bitcore.TransactionBuilder(opts) .setUnspent(unspentTest) .setOutputs(outs); -/* - outputs.forEach(function(output, i) { - var amount = output.get('amount'); - var script = { - offset: output.get('script').offset, - limit: output.get('script').limit, - buffer: output.get('script').buffer - }; - - var v = new Buffer(8); - v[0] = (amount.low >> 0) & 0xff; - v[1] = (amount.low >> 8) & 0xff; - v[2] = (amount.low >> 16) & 0xff; - v[3] = (amount.low >> 24) & 0xff; - v[4] = (amount.high >> 0) & 0xff; - v[5] = (amount.high >> 8) & 0xff; - v[6] = (amount.high >> 16) & 0xff; - v[7] = (amount.high >> 24) & 0xff; - - var s = script.buffer.slice(script.offset, script.limit); - - b.tx.outs[i].v = v; - b.tx.outs[i].s = s; - }); -*/ - var selectedUtxos = b.getSelectedUnspent(); var inputChainPaths = selectedUtxos.map(function(utxo) { return pkr.pathForAddress(utxo.address); @@ -528,46 +492,10 @@ describe('PayPro (in Wallet) model', function() { }); }); -/* - var outs = []; - outputs.forEach(function(output) { - outs.push({ - address: w.getAddressesStr()[0] || '2N6J45pqfu5y7zgWDwXDAmdd8qzK1oRdz3A', - amountSatStr: '0' - }); - }); -*/ - var b = new bitcore.TransactionBuilder(opts) .setUnspent(unspentTest) .setOutputs(outs); -/* - outputs.forEach(function(output, i) { - var amount = output.get('amount'); - var script = { - offset: output.get('script').offset, - limit: output.get('script').limit, - buffer: output.get('script').buffer - }; - - var v = new Buffer(8); - v[0] = (amount.low >> 0) & 0xff; - v[1] = (amount.low >> 8) & 0xff; - v[2] = (amount.low >> 16) & 0xff; - v[3] = (amount.low >> 24) & 0xff; - v[4] = (amount.high >> 0) & 0xff; - v[5] = (amount.high >> 8) & 0xff; - v[6] = (amount.high >> 16) & 0xff; - v[7] = (amount.high >> 24) & 0xff; - - var s = script.buffer.slice(script.offset, script.limit); - - b.tx.outs[i].v = v; - b.tx.outs[i].s = s; - }); -*/ - var selectedUtxos = b.getSelectedUnspent(); var inputChainPaths = selectedUtxos.map(function(utxo) { return pkr.pathForAddress(utxo.address); From d51c1c552f7bcf203a9287227918f36dc2050206 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Tue, 5 Aug 2014 18:12:26 -0700 Subject: [PATCH 109/178] paypro: potentially use verbatim scripts after setting outputs. --- js/models/core/Wallet.js | 10 ++++++++++ test/test.PayPro.js | 20 ++++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index 73fb086a6..86ada9890 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -1165,6 +1165,16 @@ Wallet.prototype.createPaymentTxSync = function(options, merchantData, unspent) .setUnspent(unspent) .setOutputs(outs); + // merchantData.pr.pd.outputs.forEach(function(output, i) { + // var script = { + // offset: output.script.offset, + // limit: output.script.limit, + // buffer: new Buffer(output.script.buffer, 'hex') + // }; + // 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); diff --git a/test/test.PayPro.js b/test/test.PayPro.js index a5e9186cb..d78ed883c 100644 --- a/test/test.PayPro.js +++ b/test/test.PayPro.js @@ -270,6 +270,16 @@ describe('PayPro (in Wallet) model', function() { .setUnspent(unspentTest) .setOutputs(outs); + // outputs.forEach(function(output, i) { + // var script = { + // offset: output.get('script').offset, + // limit: output.get('script').limit, + // buffer: 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); @@ -496,6 +506,16 @@ describe('PayPro (in Wallet) model', function() { .setUnspent(unspentTest) .setOutputs(outs); + // outputs.forEach(function(output, i) { + // var script = { + // offset: output.get('script').offset, + // limit: output.get('script').limit, + // buffer: 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); From 052e2522be188d7b6c15753c79bdc823fa921eb6 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Wed, 6 Aug 2014 09:58:16 -0700 Subject: [PATCH 110/178] paypro: stop using global $http. --- js/models/core/Wallet.js | 12 ++++-------- test/mocks/FakePayProServer.js | 19 +++++++------------ test/test.PayPro.js | 3 ++- 3 files changed, 13 insertions(+), 21 deletions(-) diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index 86ada9890..5712347b5 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -25,10 +25,6 @@ var TxProposals = require('./TxProposals'); var PrivateKey = require('./PrivateKey'); var copayConfig = require('../../../config'); -var G = typeof window !== 'undefined' - ? window - : global; - function Wallet(opts) { var self = this; @@ -804,7 +800,7 @@ Wallet.prototype.createPaymentTx = function(options, cb) { return; } - return $http({ + return Wallet.request({ method: options.method || 'POST', url: options.uri, headers: { @@ -1041,7 +1037,7 @@ Wallet.prototype.sendPaymentTx = function(ntxid, options, cb) { view[i] = pay[i]; } - return $http({ + return Wallet.request({ method: 'POST', url: txp.merchant.pr.pd.payment_url, headers: { @@ -1719,10 +1715,10 @@ Wallet.prototype.verifySignedJson = function(senderId, payload, signature) { // deviates from BIP-70. // if (typeof angular !== 'undefined') { -// G.$http = G.$http || angular.bootstrap().get('$http'); +// var $http = angular.bootstrap().get('$http'); // } -G.$http = G.$http || function $http(options, callback) { +Wallet.request = function(options, callback) { if (typeof options === 'string') { options = { uri: options }; } diff --git a/test/mocks/FakePayProServer.js b/test/mocks/FakePayProServer.js index 6ce42da22..24d3a4ca6 100644 --- a/test/mocks/FakePayProServer.js +++ b/test/mocks/FakePayProServer.js @@ -4,9 +4,7 @@ var is_browser = typeof process == 'undefined' || typeof process.versions === 'undefined'; var bitcore = bitcore || require('bitcore'); var PayPro = bitcore.PayPro; - -var G = is_browser ? window : global; -G.SSL_UNTRUSTED = true; +var Wallet = require('../../js/models/core/Wallet'); var x509 = { priv: '' @@ -93,17 +91,14 @@ x509.der = new Buffer(x509.der, 'base64'); x509.pem = new Buffer(x509.pem, 'base64'); function startServer(cb) { - if (G.$http && G.$http.__server) { + if (Wallet.request._server) { setTimeout(function() { - return cb(null, G.$http.__server); + return cb(null, Wallet.request._server); }, 1); return; } - var old; - if (G.$http) { - old = G.$http; - } + var old = Wallet.request; var server = { POST: { @@ -283,12 +278,12 @@ function startServer(cb) { if (cb) return cb(); }, close: function(cb) { - if (old) G.$http = old; + Wallet.request = old; return cb(); } }; - G.$http = function(options) { + Wallet.request = function(options) { var ret = { success: function(cb) { this._success = cb; @@ -332,7 +327,7 @@ function startServer(cb) { return ret; }; - G.$http.__server = server; + Wallet.request._server = server; setTimeout(function() { return cb(null, server); diff --git a/test/test.PayPro.js b/test/test.PayPro.js index d78ed883c..8b0d30bba 100644 --- a/test/test.PayPro.js +++ b/test/test.PayPro.js @@ -3,7 +3,8 @@ 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 { From f20836a24c129510fd21c05b2f2b49385c343704 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Wed, 6 Aug 2014 10:31:39 -0700 Subject: [PATCH 111/178] paypro: use server outputs for creating txs again. --- js/models/core/Wallet.js | 22 +++++++++++----------- test/test.PayPro.js | 36 ++++++++++++++++++------------------ 2 files changed, 29 insertions(+), 29 deletions(-) diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index 5712347b5..4405571dd 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -1161,15 +1161,15 @@ Wallet.prototype.createPaymentTxSync = function(options, merchantData, unspent) .setUnspent(unspent) .setOutputs(outs); - // merchantData.pr.pd.outputs.forEach(function(output, i) { - // var script = { - // offset: output.script.offset, - // limit: output.script.limit, - // buffer: new Buffer(output.script.buffer, 'hex') - // }; - // var s = script.buffer.slice(script.offset, script.limit); - // b.tx.outs[i].s = s; - // }); + merchantData.pr.pd.outputs.forEach(function(output, i) { + var script = { + offset: output.script.offset, + limit: output.script.limit, + buffer: new Buffer(output.script.buffer, 'hex') + }; + 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) { @@ -1289,10 +1289,10 @@ Wallet.prototype.verifyPaymentRequest = function(ntxid) { var av = tx.outs[i].v; // Actual script - // var as = tx.outs[i].s; + var as = tx.outs[i].s; // XXX allow changing of script as long as address is same - var as = es; + // var as = es; // XXX allow changing of script as long as address is same // var network = pd.get('network') === 'main' ? 'livenet' : 'testnet'; diff --git a/test/test.PayPro.js b/test/test.PayPro.js index 8b0d30bba..b157870d7 100644 --- a/test/test.PayPro.js +++ b/test/test.PayPro.js @@ -271,15 +271,15 @@ describe('PayPro (in Wallet) model', function() { .setUnspent(unspentTest) .setOutputs(outs); - // outputs.forEach(function(output, i) { - // var script = { - // offset: output.get('script').offset, - // limit: output.get('script').limit, - // buffer: output.get('script').buffer - // }; - // var s = script.buffer.slice(script.offset, script.limit); - // b.tx.outs[i].s = s; - // }); + outputs.forEach(function(output, i) { + var script = { + offset: output.get('script').offset, + limit: output.get('script').limit, + buffer: 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) { @@ -507,15 +507,15 @@ describe('PayPro (in Wallet) model', function() { .setUnspent(unspentTest) .setOutputs(outs); - // outputs.forEach(function(output, i) { - // var script = { - // offset: output.get('script').offset, - // limit: output.get('script').limit, - // buffer: output.get('script').buffer - // }; - // var s = script.buffer.slice(script.offset, script.limit); - // b.tx.outs[i].s = s; - // }); + outputs.forEach(function(output, i) { + var script = { + offset: output.get('script').offset, + limit: output.get('script').limit, + buffer: 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) { From d87697dfed4ecf08fb3fdca88e5edc51e6262d2e Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Wed, 6 Aug 2014 10:46:13 -0700 Subject: [PATCH 112/178] paypro: support user amount decision on 0-value-server-outputs. --- js/controllers/send.js | 7 +++++++ js/directives.js | 4 +++- js/models/core/Wallet.js | 29 +++++++++++++++++++++++++++++ 3 files changed, 39 insertions(+), 1 deletion(-) diff --git a/js/controllers/send.js b/js/controllers/send.js index 86e8831f6..30f4a9b58 100644 --- a/js/controllers/send.js +++ b/js/controllers/send.js @@ -62,6 +62,13 @@ angular.module('copayApp.controllers').controller('SendController', var w = $rootScope.wallet; function done(ntxid, merchantData) { + if (merchantData && +merchantData.total === 0) { + var txp = w.txProposals.txps[ntxid]; + txp.builder.tx.outs[0].v = bitcore.Bignum(amount + '', 10).toBuffer({ + endian: 'little', + size: 1 + }); + } if (w.isShared()) { $scope.loading = false; var message = 'The transaction proposal has been created'; diff --git a/js/directives.js b/js/directives.js index 3cc79625d..f30f5ef8b 100644 --- a/js/directives.js +++ b/js/directives.js @@ -61,7 +61,9 @@ angular.module('copayApp.directives') var amount = angular.element( document.querySelector('input#amount')); amount.val(total); - amount.attr('disabled', true); + if (+merchantData.total !== 0) { + amount.attr('disabled', true); + } var sendto = angular.element(document .querySelector('div.send-note > p[ng-class]:first-of-type')); diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index 4405571dd..2558067e2 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -1261,6 +1261,31 @@ Wallet.prototype.verifyPaymentRequest = function(ntxid) { return false; } + // Figure out whether the user is supposed + // to decide the value of the outputs. + var undecided = false; + var total = bignum('0', 10); + for (var i = 0; i < outputs.length; i++) { + var output = outputs[i]; + var amount = output.get('amount'); + 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; + total = total.add(bignum.fromBuffer(v, { + endian: 'little', + size: 1 + })); + } + if (+total.toString(10) === 0) { + undecided = true; + } + for (var i = 0; i < outputs.length; i++) { var output = outputs[i]; @@ -1299,6 +1324,10 @@ Wallet.prototype.verifyPaymentRequest = function(ntxid) { // var es = bitcore.Address.fromScriptPubKey(new bitcore.Script(es), network)[0]; // var as = bitcore.Address.fromScriptPubKey(new bitcore.Script(tx.outs[i].s), network)[0]; + if (undecided) { + av = ev = new Buffer([0]); + } + // Make sure the tx's output script and values match the payment request's. if (av.toString('hex') !== ev.toString('hex') || as.toString('hex') !== es.toString('hex')) { From 3ade6725617d1bc2c2bd482c50c1fbe24e72fbb2 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Wed, 6 Aug 2014 11:50:49 -0700 Subject: [PATCH 113/178] paypro: prepare for txp refactor. will stay reverted until #1001 is merged. --- js/controllers/send.js | 2 +- js/models/core/Wallet.js | 23 ++++++++++------------- test/test.PayPro.js | 10 +++++----- 3 files changed, 16 insertions(+), 19 deletions(-) diff --git a/js/controllers/send.js b/js/controllers/send.js index 30f4a9b58..7f5f15a89 100644 --- a/js/controllers/send.js +++ b/js/controllers/send.js @@ -63,7 +63,7 @@ angular.module('copayApp.controllers').controller('SendController', function done(ntxid, merchantData) { if (merchantData && +merchantData.total === 0) { - var txp = w.txProposals.txps[ntxid]; + var txp = w.txProposals.get(ntxid); txp.builder.tx.outs[0].v = bitcore.Bignum(amount + '', 10).toBuffer({ endian: 'little', size: 1 diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index 2558067e2..9c0a22194 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -965,7 +965,7 @@ Wallet.prototype.sendPaymentTx = function(ntxid, options, cb) { options = {}; } - var txp = this.txProposals.txps[ntxid]; + var txp = this.txProposals.get(ntxid); if (!txp) return; var tx = txp.builder.build(); @@ -1178,10 +1178,8 @@ Wallet.prototype.createPaymentTxSync = function(options, merchantData, unspent) b = b.setHashToScriptMap(pkr.getRedeemScriptMap(inputChainPaths)); - if (priv) { - var keys = priv.getForPaths(inputChainPaths); - var signed = b.sign(keys); - } + var keys = priv.getForPaths(inputChainPaths); + var signed = b.sign(keys); if (options.fetch) return; @@ -1193,15 +1191,16 @@ Wallet.prototype.createPaymentTxSync = function(options, merchantData, unspent) var myId = this.getMyCopayerId(); var now = Date.now(); - var me = {}; - var tx = b.build(); - if (priv && tx.countInputSignatures(0)) me[myId] = now; + if (!tx.countInputSignatures(0)) + throw new Error('Could not sign generated tx'); + var me = {}; + me[myId] = now; var meSeen = {}; if (priv) meSeen[myId] = now; - var data = { + var ntxid = this.txProposals.add(new TxProposal({ inputChainPaths: inputChainPaths, signedBy: me, seenBy: meSeen, @@ -1210,9 +1209,7 @@ Wallet.prototype.createPaymentTxSync = function(options, merchantData, unspent) builder: b, comment: options.memo, merchant: merchantData - }; - - var ntxid = this.txProposals.add(data); + })); return ntxid; }; @@ -1224,7 +1221,7 @@ Wallet.prototype.verifyPaymentRequest = function(ntxid) { if (!ntxid) return false; var txp = typeof ntxid !== 'object' - ? this.txProposals.txps[ntxid] + ? this.txProposals.get(ntxid) : ntxid; // If we're not a payment protocol proposal, ignore. diff --git a/test/test.PayPro.js b/test/test.PayPro.js index b157870d7..09bbf5045 100644 --- a/test/test.PayPro.js +++ b/test/test.PayPro.js @@ -782,7 +782,7 @@ describe('PayPro (in Wallet) model', function() { merchantData.raw = pr.serialize().toString('hex'); var myId = w.getMyCopayerId(); - var txp = w.txProposals.txps[ntxid]; + var txp = w.txProposals.get(ntxid); should.exist(txp); should.exist(txp.signedBy[myId]); should.not.exist(txp.rejectedBy[myId]); @@ -809,7 +809,7 @@ describe('PayPro (in Wallet) model', function() { amount.low = 2; var myId = w.getMyCopayerId(); - var txp = w.txProposals.txps[ntxid]; + var txp = w.txProposals.get(ntxid); should.exist(txp); should.exist(txp.signedBy[myId]); should.not.exist(txp.rejectedBy[myId]); @@ -830,12 +830,12 @@ describe('PayPro (in Wallet) model', function() { should.exist(merchantData); // Tamper with payment request in its abstract form: - var txp = w.txProposals.txps[ntxid]; + 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.txps[ntxid]; + var txp = w.txProposals.get(ntxid); should.exist(txp); should.exist(txp.signedBy[myId]); should.not.exist(txp.rejectedBy[myId]); @@ -856,7 +856,7 @@ describe('PayPro (in Wallet) model', function() { should.exist(merchantData); var myId = w.getMyCopayerId(); - var txp = w.txProposals.txps[ntxid]; + var txp = w.txProposals.get(ntxid); should.exist(txp); should.exist(txp.signedBy[myId]); should.not.exist(txp.rejectedBy[myId]); From b254dea911ec429939a7bf0ca1bd2ac660d51e45 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Wed, 6 Aug 2014 11:50:53 -0700 Subject: [PATCH 114/178] Revert "paypro: prepare for txp refactor. will stay reverted until #1001 is merged." This reverts commit c826acb3e539c4deb047053fd08a2a5e8fb8f8e8. --- js/controllers/send.js | 2 +- js/models/core/Wallet.js | 25 ++++++++++++++----------- test/test.PayPro.js | 10 +++++----- 3 files changed, 20 insertions(+), 17 deletions(-) diff --git a/js/controllers/send.js b/js/controllers/send.js index 7f5f15a89..30f4a9b58 100644 --- a/js/controllers/send.js +++ b/js/controllers/send.js @@ -63,7 +63,7 @@ angular.module('copayApp.controllers').controller('SendController', function done(ntxid, merchantData) { if (merchantData && +merchantData.total === 0) { - var txp = w.txProposals.get(ntxid); + var txp = w.txProposals.txps[ntxid]; txp.builder.tx.outs[0].v = bitcore.Bignum(amount + '', 10).toBuffer({ endian: 'little', size: 1 diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index 9c0a22194..2558067e2 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -965,7 +965,7 @@ Wallet.prototype.sendPaymentTx = function(ntxid, options, cb) { options = {}; } - var txp = this.txProposals.get(ntxid); + var txp = this.txProposals.txps[ntxid]; if (!txp) return; var tx = txp.builder.build(); @@ -1178,8 +1178,10 @@ Wallet.prototype.createPaymentTxSync = function(options, merchantData, unspent) b = b.setHashToScriptMap(pkr.getRedeemScriptMap(inputChainPaths)); - var keys = priv.getForPaths(inputChainPaths); - var signed = b.sign(keys); + if (priv) { + var keys = priv.getForPaths(inputChainPaths); + var signed = b.sign(keys); + } if (options.fetch) return; @@ -1191,16 +1193,15 @@ Wallet.prototype.createPaymentTxSync = function(options, merchantData, unspent) var myId = this.getMyCopayerId(); var now = Date.now(); - var tx = b.build(); - if (!tx.countInputSignatures(0)) - throw new Error('Could not sign generated tx'); - var me = {}; - me[myId] = now; + + var tx = b.build(); + if (priv && tx.countInputSignatures(0)) me[myId] = now; + var meSeen = {}; if (priv) meSeen[myId] = now; - var ntxid = this.txProposals.add(new TxProposal({ + var data = { inputChainPaths: inputChainPaths, signedBy: me, seenBy: meSeen, @@ -1209,7 +1210,9 @@ Wallet.prototype.createPaymentTxSync = function(options, merchantData, unspent) builder: b, comment: options.memo, merchant: merchantData - })); + }; + + var ntxid = this.txProposals.add(data); return ntxid; }; @@ -1221,7 +1224,7 @@ Wallet.prototype.verifyPaymentRequest = function(ntxid) { if (!ntxid) return false; var txp = typeof ntxid !== 'object' - ? this.txProposals.get(ntxid) + ? this.txProposals.txps[ntxid] : ntxid; // If we're not a payment protocol proposal, ignore. diff --git a/test/test.PayPro.js b/test/test.PayPro.js index 09bbf5045..b157870d7 100644 --- a/test/test.PayPro.js +++ b/test/test.PayPro.js @@ -782,7 +782,7 @@ describe('PayPro (in Wallet) model', function() { merchantData.raw = pr.serialize().toString('hex'); var myId = w.getMyCopayerId(); - var txp = w.txProposals.get(ntxid); + var txp = w.txProposals.txps[ntxid]; should.exist(txp); should.exist(txp.signedBy[myId]); should.not.exist(txp.rejectedBy[myId]); @@ -809,7 +809,7 @@ describe('PayPro (in Wallet) model', function() { amount.low = 2; var myId = w.getMyCopayerId(); - var txp = w.txProposals.get(ntxid); + var txp = w.txProposals.txps[ntxid]; should.exist(txp); should.exist(txp.signedBy[myId]); should.not.exist(txp.rejectedBy[myId]); @@ -830,12 +830,12 @@ describe('PayPro (in Wallet) model', function() { should.exist(merchantData); // Tamper with payment request in its abstract form: - var txp = w.txProposals.get(ntxid); + var txp = w.txProposals.txps[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); + var txp = w.txProposals.txps[ntxid]; should.exist(txp); should.exist(txp.signedBy[myId]); should.not.exist(txp.rejectedBy[myId]); @@ -856,7 +856,7 @@ describe('PayPro (in Wallet) model', function() { should.exist(merchantData); var myId = w.getMyCopayerId(); - var txp = w.txProposals.get(ntxid); + var txp = w.txProposals.txps[ntxid]; should.exist(txp); should.exist(txp.signedBy[myId]); should.not.exist(txp.rejectedBy[myId]); From 5c97649f83fe83278e17cf09c49bc66066cb0c81 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Wed, 6 Aug 2014 12:05:06 -0700 Subject: [PATCH 115/178] paypro: run paypro tests in browser. whitespace. --- test/index.html | 3 ++- util/build.js | 9 +++------ 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/test/index.html b/test/index.html index 01de4001a..f755dc486 100644 --- a/test/index.html +++ b/test/index.html @@ -12,11 +12,12 @@ - + + diff --git a/util/build.js b/util/build.js index f1a7a2f98..4ac9317ac 100644 --- a/util/build.js +++ b/util/build.js @@ -75,11 +75,8 @@ var createBundle = function(opts) { b.require('./test/mocks/FakeNetwork', { expose: './mocks/FakeNetwork' }); - b.require('./test/mocks/FakeBuilder', { - expose: './mocks/FakeBuilder' - }); b.require('./js/models/network/WebRTC', { - expose: '../js/models/network/WebRTC' + expose: '../js/models/network/WebRTC' }); b.require('./js/models/blockchain/Insight', { expose: '../js/models/blockchain/Insight' @@ -98,10 +95,10 @@ var createBundle = function(opts) { }); b.require('./config', { expose: '../config' - }); + }); b.require('./js/models/core/HDPath', { expose: '../js/models/core/HDPath' - }); + }); if (opts.debug) { //include dev dependencies From bc716646dc29cac5d093fa0ebf3495c585b5e4a9 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Wed, 6 Aug 2014 12:27:07 -0700 Subject: [PATCH 116/178] paypro: figure out why paypro tests are hanging in the browser. --- test/test.PayPro.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/test/test.PayPro.js b/test/test.PayPro.js index b157870d7..e2604fc4a 100644 --- a/test/test.PayPro.js +++ b/test/test.PayPro.js @@ -255,8 +255,9 @@ describe('PayPro (in Wallet) model', function() { v[7] = (amount.low >> 0) & 0xff; var s = script.buffer.slice(script.offset, script.limit); - var network = network === 'main' ? 'livenet' : 'testnet'; - var addr = bitcore.Address.fromScriptPubKey(new bitcore.Script(s), network); + var net = network === 'main' ? 'livenet' : 'testnet'; + // XXX browser test chokes here on new bitcore.Script: + var addr = bitcore.Address.fromScriptPubKey(new bitcore.Script(s), net); outs.push({ address: addr[0].toString(), @@ -491,8 +492,8 @@ describe('PayPro (in Wallet) model', function() { v[7] = (amount.low >> 0) & 0xff; var s = script.buffer.slice(script.offset, script.limit); - var network = network === 'main' ? 'livenet' : 'testnet'; - var addr = bitcore.Address.fromScriptPubKey(new bitcore.Script(s), network); + var net = network === 'main' ? 'livenet' : 'testnet'; + var addr = bitcore.Address.fromScriptPubKey(new bitcore.Script(s), net); outs.push({ address: addr[0].toString(), From 6f4af886621027c1717dbf3b3b3a4017ff658b2d Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Wed, 6 Aug 2014 12:37:06 -0700 Subject: [PATCH 117/178] paypro: tests - fix browser hanging. --- test/test.PayPro.js | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/test/test.PayPro.js b/test/test.PayPro.js index e2604fc4a..cd17d7056 100644 --- a/test/test.PayPro.js +++ b/test/test.PayPro.js @@ -241,7 +241,8 @@ describe('PayPro (in Wallet) model', function() { var script = { offset: output.get('script').offset, limit: output.get('script').limit, - buffer: output.get('script').buffer + buffer: new Buffer(new Uint8Array( + output.get('script').buffer)) }; var v = new Buffer(8); @@ -256,7 +257,6 @@ describe('PayPro (in Wallet) model', function() { var s = script.buffer.slice(script.offset, script.limit); var net = network === 'main' ? 'livenet' : 'testnet'; - // XXX browser test chokes here on new bitcore.Script: var addr = bitcore.Address.fromScriptPubKey(new bitcore.Script(s), net); outs.push({ @@ -276,7 +276,8 @@ describe('PayPro (in Wallet) model', function() { var script = { offset: output.get('script').offset, limit: output.get('script').limit, - buffer: output.get('script').buffer + buffer: new Buffer(new Uint8Array( + output.get('script').buffer)) }; var s = script.buffer.slice(script.offset, script.limit); b.tx.outs[i].s = s; @@ -478,7 +479,8 @@ describe('PayPro (in Wallet) model', function() { var script = { offset: output.get('script').offset, limit: output.get('script').limit, - buffer: output.get('script').buffer + buffer: new Buffer(new Uint8Array( + output.get('script').buffer)) }; var v = new Buffer(8); @@ -512,7 +514,8 @@ describe('PayPro (in Wallet) model', function() { var script = { offset: output.get('script').offset, limit: output.get('script').limit, - buffer: output.get('script').buffer + buffer: new Buffer(new Uint8Array( + output.get('script').buffer)) }; var s = script.buffer.slice(script.offset, script.limit); b.tx.outs[i].s = s; From 3931e73da4dc670fb91ff953462a231745f9e08f Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Wed, 6 Aug 2014 12:49:48 -0700 Subject: [PATCH 118/178] paypro: debugging endianness issue in bignum (browser vs node). --- js/models/core/Wallet.js | 4 ++++ test/test.PayPro.js | 44 +++++++++++++++++++++++++--------------- 2 files changed, 32 insertions(+), 16 deletions(-) diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index 2558067e2..e89dc437e 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -1122,6 +1122,7 @@ Wallet.prototype.createPaymentTxSync = function(options, merchantData, unspent) merchantData.pr.pd.outputs.forEach(function(output) { var amount = output.amount; + // big endian var v = new Buffer(8); v[0] = (amount.high >> 24) & 0xff; v[1] = (amount.high >> 16) & 0xff; @@ -1268,6 +1269,7 @@ Wallet.prototype.verifyPaymentRequest = function(ntxid) { for (var i = 0; i < outputs.length; i++) { var output = outputs[i]; var amount = output.get('amount'); + // big endian var v = new Buffer(8); v[0] = (amount.high >> 24) & 0xff; v[1] = (amount.high >> 16) & 0xff; @@ -1297,6 +1299,7 @@ Wallet.prototype.verifyPaymentRequest = function(ntxid) { }; // Expected value + // little endian var ev = new Buffer(8); ev[0] = (amount.low >> 0) & 0xff; ev[1] = (amount.low >> 8) & 0xff; @@ -1342,6 +1345,7 @@ Wallet.prototype.verifyPaymentRequest = function(ntxid) { var ro = txp.merchant.pr.pd.outputs[i]; // Actual value + // little endian var av = new Buffer(8); av[0] = (ro.amount.low >> 0) & 0xff; av[1] = (ro.amount.low >> 8) & 0xff; diff --git a/test/test.PayPro.js b/test/test.PayPro.js index cd17d7056..309107a4b 100644 --- a/test/test.PayPro.js +++ b/test/test.PayPro.js @@ -245,15 +245,16 @@ describe('PayPro (in Wallet) model', function() { output.get('script').buffer)) }; + // little 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; + v[0] = (amount.low >> 0) & 0xff; + v[1] = (amount.low >> 8) & 0xff; + v[2] = (amount.low >> 16) & 0xff; + v[3] = (amount.low >> 24) & 0xff; + v[4] = (amount.high >> 0) & 0xff; + v[5] = (amount.high >> 8) & 0xff; + v[6] = (amount.high >> 16) & 0xff; + v[7] = (amount.high >> 24) & 0xff; var s = script.buffer.slice(script.offset, script.limit); var net = network === 'main' ? 'livenet' : 'testnet'; @@ -262,10 +263,16 @@ describe('PayPro (in Wallet) model', function() { 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: 'little', size: 1 }).toString(10) }); + + console.log('Output 1:'); + console.log('Buffer: ' + v.toString('hex')); + console.log(outs[outs.length - 1]); }); var b = new bitcore.TransactionBuilder(opts) @@ -483,15 +490,16 @@ describe('PayPro (in Wallet) model', function() { output.get('script').buffer)) }; + // little 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; + v[0] = (amount.low >> 0) & 0xff; + v[1] = (amount.low >> 8) & 0xff; + v[2] = (amount.low >> 16) & 0xff; + v[3] = (amount.low >> 24) & 0xff; + v[4] = (amount.high >> 0) & 0xff; + v[5] = (amount.high >> 8) & 0xff; + v[6] = (amount.high >> 16) & 0xff; + v[7] = (amount.high >> 24) & 0xff; var s = script.buffer.slice(script.offset, script.limit); var net = network === 'main' ? 'livenet' : 'testnet'; @@ -504,6 +512,10 @@ describe('PayPro (in Wallet) model', function() { size: 1 }).toString(10) }); + + console.log('Output 2:'); + console.log('Buffer: ' + v.toString('hex')); + console.log(outs[outs.length - 1]); }); var b = new bitcore.TransactionBuilder(opts) From f9bd8c3a04d098f37c07a99229e5cf965298733c Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Wed, 6 Aug 2014 13:07:16 -0700 Subject: [PATCH 119/178] paypro: try to keep everything big endian to avoid bignum bug in node. --- js/models/core/Wallet.js | 25 +++++--- test/test.PayPro.js | 123 ++++++++++++++++++++++++++++++--------- 2 files changed, 115 insertions(+), 33 deletions(-) diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index e89dc437e..15e39040a 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -979,10 +979,21 @@ Wallet.prototype.sendPaymentTx = function(ntxid, options, cb) { if (options.refund_to) { var total = txp.merchant.pr.pd.outputs.reduce(function(total, _, i) { - return total.add(bignum.fromBuffer(tx.outs[i].v, { - endian: 'little', + // 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 })); + + // XXX potential problem: bignum seems bugged in node - tx outputs use + // little endian, but fromBuffer(endian=little) ends up being big endian + // return total.add(bignum.fromBuffer(tx.outs[i].v, { + // endian: 'little', + // size: 1 + // })); }, bignum('0', 10)); var rpo = new PayPro(); @@ -1145,13 +1156,13 @@ Wallet.prototype.createPaymentTxSync = function(options, merchantData, unspent) outs.push({ address: addr[0].toString(), amountSatStr: bignum.fromBuffer(v, { - endian: 'little', + endian: 'big', size: 1 }).toString(10) }); merchantData.total = merchantData.total.add(bignum.fromBuffer(v, { - endian: 'little', + endian: 'big', size: 1 })); }); @@ -1280,7 +1291,7 @@ Wallet.prototype.verifyPaymentRequest = function(ntxid) { v[6] = (amount.low >> 8) & 0xff; v[7] = (amount.low >> 0) & 0xff; total = total.add(bignum.fromBuffer(v, { - endian: 'little', + endian: 'big', size: 1 })); } @@ -1299,7 +1310,7 @@ Wallet.prototype.verifyPaymentRequest = function(ntxid) { }; // Expected value - // little endian + // little endian (keep this LE to compare with tx output value) var ev = new Buffer(8); ev[0] = (amount.low >> 0) & 0xff; ev[1] = (amount.low >> 8) & 0xff; @@ -1345,7 +1356,7 @@ Wallet.prototype.verifyPaymentRequest = function(ntxid) { var ro = txp.merchant.pr.pd.outputs[i]; // Actual value - // little endian + // little endian (keep this LE to compare with the ev above) var av = new Buffer(8); av[0] = (ro.amount.low >> 0) & 0xff; av[1] = (ro.amount.low >> 8) & 0xff; diff --git a/test/test.PayPro.js b/test/test.PayPro.js index 309107a4b..e276eb079 100644 --- a/test/test.PayPro.js +++ b/test/test.PayPro.js @@ -21,6 +21,7 @@ var TransactionBuilder = bitcore.TransactionBuilder; var Transaction = bitcore.Transaction; var Address = bitcore.Address; var PayPro = bitcore.PayPro; +var bignum = bitcore.Bignum; var startServer = require('./mocks/FakePayProServer'); var server; @@ -246,15 +247,26 @@ describe('PayPro (in Wallet) model', function() { }; // little endian + // var v = new Buffer(8); + // v[0] = (amount.low >> 0) & 0xff; + // v[1] = (amount.low >> 8) & 0xff; + // v[2] = (amount.low >> 16) & 0xff; + // v[3] = (amount.low >> 24) & 0xff; + // v[4] = (amount.high >> 0) & 0xff; + // v[5] = (amount.high >> 8) & 0xff; + // v[6] = (amount.high >> 16) & 0xff; + // v[7] = (amount.high >> 24) & 0xff; + + // big endian var v = new Buffer(8); - v[0] = (amount.low >> 0) & 0xff; - v[1] = (amount.low >> 8) & 0xff; - v[2] = (amount.low >> 16) & 0xff; - v[3] = (amount.low >> 24) & 0xff; - v[4] = (amount.high >> 0) & 0xff; - v[5] = (amount.high >> 8) & 0xff; - v[6] = (amount.high >> 16) & 0xff; - v[7] = (amount.high >> 24) & 0xff; + 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'; @@ -265,7 +277,8 @@ describe('PayPro (in Wallet) model', function() { amountSatStr: bitcore.Bignum.fromBuffer(v, { // XXX for some reason, endian is ALWAYS 'big' // in node (in the browser it behaves correctly) - endian: 'little', + // endian: 'little', + endian: 'big', size: 1 }).toString(10) }); @@ -309,10 +322,21 @@ describe('PayPro (in Wallet) model', function() { var refund_to = w.publicKeyRing.getPubKeys(0, false, w.getMyCopayerId())[0]; var total = outputs.reduce(function(total, _, i) { - return total.add(bitcore.Bignum.fromBuffer(tx.outs[i].v, { - endian: 'little', + // 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 })); + + // XXX potential problem: bignum seems bugged in node - tx outputs use + // little endian, but fromBuffer(endian=little) ends up being big endian + // return total.add(bitcore.Bignum.fromBuffer(tx.outs[i].v, { + // endian: 'little', + // size: 1 + // })); }, bitcore.Bignum('0', 10)); var rpo = new PayPro(); @@ -397,10 +421,21 @@ describe('PayPro (in Wallet) model', function() { } var ackTotal = outputs.reduce(function(total, _, i) { - return total.add(bitcore.Bignum.fromBuffer(tx.outs[i].v, { - endian: 'little', + // 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 })); + + // XXX potential problem: bignum seems bugged in node - tx outputs use + // little endian, but fromBuffer(endian=little) ends up being big endian + // return total.add(bitcore.Bignum.fromBuffer(tx.outs[i].v, { + // endian: 'little', + // size: 1 + // })); }, bitcore.Bignum('0', 10)); ackTotal.toString(10).should.equal(total.toString(10)); @@ -491,15 +526,26 @@ describe('PayPro (in Wallet) model', function() { }; // little endian + // var v = new Buffer(8); + // v[0] = (amount.low >> 0) & 0xff; + // v[1] = (amount.low >> 8) & 0xff; + // v[2] = (amount.low >> 16) & 0xff; + // v[3] = (amount.low >> 24) & 0xff; + // v[4] = (amount.high >> 0) & 0xff; + // v[5] = (amount.high >> 8) & 0xff; + // v[6] = (amount.high >> 16) & 0xff; + // v[7] = (amount.high >> 24) & 0xff; + + // big endian var v = new Buffer(8); - v[0] = (amount.low >> 0) & 0xff; - v[1] = (amount.low >> 8) & 0xff; - v[2] = (amount.low >> 16) & 0xff; - v[3] = (amount.low >> 24) & 0xff; - v[4] = (amount.high >> 0) & 0xff; - v[5] = (amount.high >> 8) & 0xff; - v[6] = (amount.high >> 16) & 0xff; - v[7] = (amount.high >> 24) & 0xff; + 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'; @@ -508,7 +554,10 @@ describe('PayPro (in Wallet) model', function() { outs.push({ address: addr[0].toString(), amountSatStr: bitcore.Bignum.fromBuffer(v, { - endian: 'little', + // XXX for some reason, endian is ALWAYS 'big' + // in node (in the browser it behaves correctly) + // endian: 'little', + endian: 'big', size: 1 }).toString(10) }); @@ -552,10 +601,21 @@ describe('PayPro (in Wallet) model', function() { var refund_to = w.publicKeyRing.getPubKeys(0, false, w.getMyCopayerId())[0]; var total = outputs.reduce(function(total, _, i) { - return total.add(bitcore.Bignum.fromBuffer(tx.outs[i].v, { - endian: 'little', + // 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 })); + + // XXX potential problem: bignum seems bugged in node - tx outputs use + // little endian, but fromBuffer(endian=little) ends up being big endian + // return total.add(bitcore.Bignum.fromBuffer(tx.outs[i].v, { + // endian: 'little', + // size: 1 + // })); }, bitcore.Bignum('0', 10)); var rpo = new PayPro(); @@ -640,10 +700,21 @@ describe('PayPro (in Wallet) model', function() { } var ackTotal = outputs.reduce(function(total, _, i) { - return total.add(bitcore.Bignum.fromBuffer(tx.outs[i].v, { - endian: 'little', + // 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 })); + + // XXX potential problem: bignum seems bugged in node - tx outputs use + // little endian, but fromBuffer(endian=little) ends up being big endian + // return total.add(bitcore.Bignum.fromBuffer(tx.outs[i].v, { + // endian: 'little', + // size: 1 + // })); }, bitcore.Bignum('0', 10)); ackTotal.toString(10).should.equal(total.toString(10)); From 8bebbe8ec6c397cbc37e7013c5ce06fcc3ceea44 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Wed, 6 Aug 2014 13:17:18 -0700 Subject: [PATCH 120/178] paypro: fix mock server to handle txs as buffers when browserified. tests passing in browser. --- test/mocks/FakePayProServer.js | 1 + 1 file changed, 1 insertion(+) diff --git a/test/mocks/FakePayProServer.js b/test/mocks/FakePayProServer.js index 24d3a4ca6..6f0f76138 100644 --- a/test/mocks/FakePayProServer.js +++ b/test/mocks/FakePayProServer.js @@ -256,6 +256,7 @@ function startServer(cb) { res.headers['Content-Transfer-Encoding'] = 'binary'; transactions = transactions.map(function(tx) { + 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); From ab06e8b4ea7d29c861520840c749365be96ee937 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Wed, 6 Aug 2014 16:30:58 -0700 Subject: [PATCH 121/178] Revert "paypro: deal with no unspent outputs. see #1043." This reverts commit 6b5a618f0f7155c11ee734ef54ab3876b5923768. --- js/directives.js | 6 ------ js/models/core/Wallet.js | 3 --- 2 files changed, 9 deletions(-) diff --git a/js/directives.js b/js/directives.js index f30f5ef8b..bf27a4620 100644 --- a/js/directives.js +++ b/js/directives.js @@ -17,12 +17,6 @@ angular.module('copayApp.directives') // Is this a payment protocol URI (BIP-72)? if (uri && uri.merchant) { scope.wallet.fetchPaymentTx(uri.merchant, function(err, merchantData) { - if ((err && err.message === 'No unspent outputs.') - || scope.availableBalance < +merchantData.total) { - ctrl.$setValidity('validAddress', false); - return; - } - if (err) { if (scope._resetPayPro) scope._resetPayPro(); ctrl.$setValidity('validAddress', false); diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index 15e39040a..c6a114c91 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -933,9 +933,6 @@ Wallet.prototype.receivePaymentRequest = function(options, pr, cb) { return this.getUnspent(function(err, unspent) { if (options.fetch) { - if (!unspent || !unspent.length) { - return cb(new Error('No unspent outputs.')); - } self.createPaymentTxSync(options, merchantData, unspent); return cb(null, merchantData, pr); } From 2b5102a493c665147f1f275d922b913d236c10df Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Wed, 6 Aug 2014 16:40:32 -0700 Subject: [PATCH 122/178] paypro: tentatively reimplement commit that caused regression. --- js/models/core/Wallet.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index c6a114c91..15e39040a 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -933,6 +933,9 @@ Wallet.prototype.receivePaymentRequest = function(options, pr, cb) { return this.getUnspent(function(err, unspent) { if (options.fetch) { + if (!unspent || !unspent.length) { + return cb(new Error('No unspent outputs.')); + } self.createPaymentTxSync(options, merchantData, unspent); return cb(null, merchantData, pr); } From f31a59e0b3764c547aff637cf5a56d7e7ef2d109 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Wed, 6 Aug 2014 16:42:44 -0700 Subject: [PATCH 123/178] paypro: another tentative commit to avoid regression. --- js/directives.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/js/directives.js b/js/directives.js index bf27a4620..7e1060849 100644 --- a/js/directives.js +++ b/js/directives.js @@ -17,6 +17,12 @@ angular.module('copayApp.directives') // Is this a payment protocol URI (BIP-72)? if (uri && uri.merchant) { scope.wallet.fetchPaymentTx(uri.merchant, function(err, merchantData) { + if ((err && err.message === 'No unspent outputs.')) { + //|| scope.availableBalance < +merchantData.total) { + ctrl.$setValidity('validAddress', false); + return; + } + if (err) { if (scope._resetPayPro) scope._resetPayPro(); ctrl.$setValidity('validAddress', false); From 1035adffab9f50846749a3d8d60beb040bd66602 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Wed, 6 Aug 2014 16:58:02 -0700 Subject: [PATCH 124/178] paypro: fix balance check regression in directive. --- js/directives.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/js/directives.js b/js/directives.js index 7e1060849..c0db435b4 100644 --- a/js/directives.js +++ b/js/directives.js @@ -1,8 +1,8 @@ 'use strict'; angular.module('copayApp.directives') - .directive('validAddress', [ - function() { + .directive('validAddress', ['$rootScope', + function($rootScope) { var bitcore = require('bitcore'); var Address = bitcore.Address; @@ -17,8 +17,11 @@ angular.module('copayApp.directives') // Is this a payment protocol URI (BIP-72)? if (uri && uri.merchant) { scope.wallet.fetchPaymentTx(uri.merchant, function(err, merchantData) { - if ((err && err.message === 'No unspent outputs.')) { - //|| scope.availableBalance < +merchantData.total) { + var balance = $rootScope.availableBalance; + var available = +(balance * config.unitToSatoshi).toFixed(0); + + if ((err && err.message === 'No unspent outputs.') + || available < +merchantData.total) { ctrl.$setValidity('validAddress', false); return; } From 17f3402dab2c708994e7d56fdb95c470b387e80f Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Wed, 6 Aug 2014 17:17:01 -0700 Subject: [PATCH 125/178] Revert "Revert "paypro: prepare for txp refactor. will stay reverted until #1001 is merged."" This reverts commit 6fcd27b3ea0f7aa1ef7985560ca917393510c65e. --- js/controllers/send.js | 2 +- js/models/core/Wallet.js | 23 ++++++++++------------- test/test.PayPro.js | 10 +++++----- 3 files changed, 16 insertions(+), 19 deletions(-) diff --git a/js/controllers/send.js b/js/controllers/send.js index 30f4a9b58..7f5f15a89 100644 --- a/js/controllers/send.js +++ b/js/controllers/send.js @@ -63,7 +63,7 @@ angular.module('copayApp.controllers').controller('SendController', function done(ntxid, merchantData) { if (merchantData && +merchantData.total === 0) { - var txp = w.txProposals.txps[ntxid]; + var txp = w.txProposals.get(ntxid); txp.builder.tx.outs[0].v = bitcore.Bignum(amount + '', 10).toBuffer({ endian: 'little', size: 1 diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index 15e39040a..98664efcd 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -965,7 +965,7 @@ Wallet.prototype.sendPaymentTx = function(ntxid, options, cb) { options = {}; } - var txp = this.txProposals.txps[ntxid]; + var txp = this.txProposals.get(ntxid); if (!txp) return; var tx = txp.builder.build(); @@ -1190,10 +1190,8 @@ Wallet.prototype.createPaymentTxSync = function(options, merchantData, unspent) b = b.setHashToScriptMap(pkr.getRedeemScriptMap(inputChainPaths)); - if (priv) { - var keys = priv.getForPaths(inputChainPaths); - var signed = b.sign(keys); - } + var keys = priv.getForPaths(inputChainPaths); + var signed = b.sign(keys); if (options.fetch) return; @@ -1205,15 +1203,16 @@ Wallet.prototype.createPaymentTxSync = function(options, merchantData, unspent) var myId = this.getMyCopayerId(); var now = Date.now(); - var me = {}; - var tx = b.build(); - if (priv && tx.countInputSignatures(0)) me[myId] = now; + if (!tx.countInputSignatures(0)) + throw new Error('Could not sign generated tx'); + var me = {}; + me[myId] = now; var meSeen = {}; if (priv) meSeen[myId] = now; - var data = { + var ntxid = this.txProposals.add(new TxProposal({ inputChainPaths: inputChainPaths, signedBy: me, seenBy: meSeen, @@ -1222,9 +1221,7 @@ Wallet.prototype.createPaymentTxSync = function(options, merchantData, unspent) builder: b, comment: options.memo, merchant: merchantData - }; - - var ntxid = this.txProposals.add(data); + })); return ntxid; }; @@ -1236,7 +1233,7 @@ Wallet.prototype.verifyPaymentRequest = function(ntxid) { if (!ntxid) return false; var txp = typeof ntxid !== 'object' - ? this.txProposals.txps[ntxid] + ? this.txProposals.get(ntxid) : ntxid; // If we're not a payment protocol proposal, ignore. diff --git a/test/test.PayPro.js b/test/test.PayPro.js index e276eb079..962fab433 100644 --- a/test/test.PayPro.js +++ b/test/test.PayPro.js @@ -869,7 +869,7 @@ describe('PayPro (in Wallet) model', function() { merchantData.raw = pr.serialize().toString('hex'); var myId = w.getMyCopayerId(); - var txp = w.txProposals.txps[ntxid]; + var txp = w.txProposals.get(ntxid); should.exist(txp); should.exist(txp.signedBy[myId]); should.not.exist(txp.rejectedBy[myId]); @@ -896,7 +896,7 @@ describe('PayPro (in Wallet) model', function() { amount.low = 2; var myId = w.getMyCopayerId(); - var txp = w.txProposals.txps[ntxid]; + var txp = w.txProposals.get(ntxid); should.exist(txp); should.exist(txp.signedBy[myId]); should.not.exist(txp.rejectedBy[myId]); @@ -917,12 +917,12 @@ describe('PayPro (in Wallet) model', function() { should.exist(merchantData); // Tamper with payment request in its abstract form: - var txp = w.txProposals.txps[ntxid]; + 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.txps[ntxid]; + var txp = w.txProposals.get(ntxid); should.exist(txp); should.exist(txp.signedBy[myId]); should.not.exist(txp.rejectedBy[myId]); @@ -943,7 +943,7 @@ describe('PayPro (in Wallet) model', function() { should.exist(merchantData); var myId = w.getMyCopayerId(); - var txp = w.txProposals.txps[ntxid]; + var txp = w.txProposals.get(ntxid); should.exist(txp); should.exist(txp.signedBy[myId]); should.not.exist(txp.rejectedBy[myId]); From fcaf911671915dfd8f67d29a59e46e3e7efe901e Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Wed, 6 Aug 2014 17:27:07 -0700 Subject: [PATCH 126/178] paypro: include merchant data in tx proposal again. --- js/models/core/TxProposal.js | 1 + 1 file changed, 1 insertion(+) diff --git a/js/models/core/TxProposal.js b/js/models/core/TxProposal.js index 3ddeac22e..fb258af51 100644 --- a/js/models/core/TxProposal.js +++ b/js/models/core/TxProposal.js @@ -36,6 +36,7 @@ function TxProposal(opts) { this.sentTxid = opts.sentTxid || null; this.comment = opts.comment || null; this.readonly = opts.readonly || null; + this.merchant = opts.merchant || null; this._sync(); } From a8ab0126019a2178a6ed86072f0601ea64f7a164 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Wed, 6 Aug 2014 17:27:22 -0700 Subject: [PATCH 127/178] paypro: fix tests for latest master rebase. --- test/test.PayPro.js | 36 +++++++++++++++++++++--------------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/test/test.PayPro.js b/test/test.PayPro.js index 962fab433..3afc15c6f 100644 --- a/test/test.PayPro.js +++ b/test/test.PayPro.js @@ -12,7 +12,7 @@ if (is_browser) { } var copayConfig = require('../config'); var Wallet = require('../js/models/core/Wallet'); -var Structure = copay.Structure; +var PrivateKey = copay.PrivateKey; var Storage = require('./mocks/FakeStorage'); var Network = require('./mocks/FakeNetwork'); var Blockchain = require('./mocks/FakeBlockchain'); @@ -26,20 +26,27 @@ var startServer = require('./mocks/FakePayProServer'); var server; -describe('PayPro (in Wallet) model', function() { - var config = { - requiredCopayers: 1, - totalCopayers: 1, - spendUnconfirmed: true, - reconnectDelay: 100, - networkName: 'testnet', - }; +var config = { + requiredCopayers: 1, + totalCopayers: 1, + spendUnconfirmed: true, + reconnectDelay: 100, + networkName: 'testnet', +}; - var createW = function(netKey, N, conf) { +var getNewEpk = function() { + return new PrivateKey({ + networkName: config.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 (netKey) c.netKey = netKey; var mainPrivateKey = new copay.PrivateKey({ networkName: config.networkName }); @@ -109,8 +116,7 @@ describe('PayPro (in Wallet) model', function() { var createW2 = function(privateKeys, N, conf) { if (!N) N = 3; - var netKey = 'T0FbU2JLby0='; - var w = createW(netKey, N, conf); + var w = createW(N, conf); should.exist(w); var pkr = w.publicKeyRing; @@ -118,9 +124,9 @@ describe('PayPro (in Wallet) model', function() { for (var i = 0; i < N - 1; i++) { if (privateKeys) { var k = privateKeys[i]; - pkr.addCopayer(k ? k.deriveBIP45Branch().extendedPublicKeyString() : null); + pkr.addCopayer(k ? k.deriveBIP45Branch().extendedPublicKeyString() : getNewEpk()); } else { - pkr.addCopayer(); + pkr.addCopayer(getNewEpk()); } } From d4f4f99dce69deea8ed7d3423de1076ab6706586 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Wed, 6 Aug 2014 17:54:14 -0700 Subject: [PATCH 128/178] paypro: minor - comment. --- js/directives.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/js/directives.js b/js/directives.js index c0db435b4..9078a5099 100644 --- a/js/directives.js +++ b/js/directives.js @@ -22,6 +22,8 @@ angular.module('copayApp.directives') if ((err && err.message === 'No unspent outputs.') || available < +merchantData.total) { + // TODO: Actually display a notification window here + // instead of simply saying the URI is invalid. ctrl.$setValidity('validAddress', false); return; } From d58bfe9de88be727ba4d61be6752665c49216112 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Wed, 6 Aug 2014 23:02:31 -0700 Subject: [PATCH 129/178] paypro: comment for handling 0-amount outputs from server. --- js/controllers/send.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/js/controllers/send.js b/js/controllers/send.js index 7f5f15a89..08a1dcac3 100644 --- a/js/controllers/send.js +++ b/js/controllers/send.js @@ -62,13 +62,19 @@ angular.module('copayApp.controllers').controller('SendController', var w = $rootScope.wallet; function done(ntxid, merchantData) { + // If user is granted the privilege of choosing + // their own amount, add it to the tx. if (merchantData && +merchantData.total === 0) { var txp = w.txProposals.get(ntxid); - txp.builder.tx.outs[0].v = bitcore.Bignum(amount + '', 10).toBuffer({ + var tx = txp.builder.tx = txp.builder.tx || txp.builder.build(); + tx.outs[0].v = bitcore.Bignum(amount + '', 10).toBuffer({ + // XXX This may not work in node due + // to the bignum only-big endian bug: endian: 'little', size: 1 }); } + if (w.isShared()) { $scope.loading = false; var message = 'The transaction proposal has been created'; From 80ceca3b73006890b078d5420e7017cd1e3a25fd Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Thu, 7 Aug 2014 09:17:03 -0700 Subject: [PATCH 130/178] paypro: minor refactor - clean up whitespace in directives. --- js/directives.js | 247 ++++++++++++++++++++++++----------------------- 1 file changed, 127 insertions(+), 120 deletions(-) diff --git a/js/directives.js b/js/directives.js index 9078a5099..21432b046 100644 --- a/js/directives.js +++ b/js/directives.js @@ -12,146 +12,153 @@ angular.module('copayApp.directives') require: 'ngModel', link: function(scope, elem, attrs, ctrl) { var validator = function(value) { - var uri = copay.HDPath.parseBitcoinURI(value); + var uri; - // Is this a payment protocol URI (BIP-72)? - if (uri && uri.merchant) { - scope.wallet.fetchPaymentTx(uri.merchant, function(err, merchantData) { - var balance = $rootScope.availableBalance; - var available = +(balance * config.unitToSatoshi).toFixed(0); + if (/^https?:\/\//.test(value)) { + uri = { merchant: value }; + } else { + uri = copay.HDPath.parseBitcoinURI(value); + } - if ((err && err.message === 'No unspent outputs.') - || available < +merchantData.total) { - // TODO: Actually display a notification window here - // instead of simply saying the URI is invalid. - ctrl.$setValidity('validAddress', false); - return; - } + // Regular Address + if (!uri || !uri.merchant) { + var a = new Address(value); + ctrl.$setValidity('validAddress', a.isValid() && a.network().name === config.networkName); + return value; + } - if (err) { - if (scope._resetPayPro) scope._resetPayPro(); - ctrl.$setValidity('validAddress', false); - return; - } + // Payment Protocol URI (BIP-72) + scope.wallet.fetchPaymentTx(uri.merchant, function(err, merchantData) { + var balance = $rootScope.availableBalance; + var available = +(balance * config.unitToSatoshi).toFixed(0); - var expires = new Date(merchantData.pr.pd.expires * 1000); - var memo = merchantData.pr.pd.memo; - var payment_url = merchantData.pr.pd.payment_url; - var total = merchantData.total; + if ((err && err.message === 'No unspent outputs.') + || available < +merchantData.total) { + // TODO: Actually display a notification window here + // instead of simply saying the URI is invalid. + ctrl.$setValidity('validAddress', false); + return; + } - if (typeof total === 'string') { - total = bignum(total, 10).toBuffer({ - endian: 'little', - size: 1 - }); - } + if (err) { + if (scope._resetPayPro) scope._resetPayPro(); + ctrl.$setValidity('validAddress', false); + return; + } - total = bignum - .fromBuffer(total, { - endian: 'little', - size: 1 - }) - .toString(10); + var expires = new Date(merchantData.pr.pd.expires * 1000); + var memo = merchantData.pr.pd.memo; + var payment_url = merchantData.pr.pd.payment_url; + var total = merchantData.total; - // XXX There needs to be a better way to do this: - total = +total / config.unitToSatoshi; + if (typeof total === 'string') { + total = bignum(total, 10).toBuffer({ + endian: 'little', + size: 1 + }); + } - // XXX Pretty much all of this code accesses the raw DOM. It's - // very bad, there's probably a better, more angular-y way to - // do things here. + total = bignum + .fromBuffer(total, { + endian: 'little', + size: 1 + }) + .toString(10); - var address = angular.element( - document.querySelector('input#address')); + // XXX There needs to be a better way to do this: + total = +total / config.unitToSatoshi; - var amount = angular.element( - document.querySelector('input#amount')); - amount.val(total); - if (+merchantData.total !== 0) { - amount.attr('disabled', true); - } + // XXX Pretty much all of this code accesses the raw DOM. It's + // very bad, there's probably a better, more angular-y way to + // do things here. - var sendto = angular.element(document - .querySelector('div.send-note > p[ng-class]:first-of-type')); - sendto.html(sendto.html() + '
Server: ' + memo); + var address = angular.element( + document.querySelector('input#address')); - var tamount = angular.element(document - .querySelector('div.send-note > p[ng-class]:nth-of-type(2)')); - var ca = merchantData.pr.ca - || 'Untrusted'; - tamount.attr('class', - tamount.attr('class').replace(' hidden', '')) - tamount.html(total + ' (CA: ' + ca - + '. Expires: ' - + expires.toISOString() - + ')'); + var amount = angular.element( + document.querySelector('input#amount')); + amount.val(total); + if (+merchantData.total !== 0) { + amount.attr('disabled', true); + } - var submit = angular.element( - document.querySelector('button[type=submit]')); - submit.attr('disabled', false); + var sendto = angular.element(document + .querySelector('div.send-note > p[ng-class]:first-of-type')); + sendto.html(sendto.html() + '
Server: ' + memo); - var sendall = angular.element( - document.querySelector('[title="Send all funds"]')); - sendall.attr('class', sendall.attr('class') + ' hidden'); + var tamount = angular.element(document + .querySelector('div.send-note > p[ng-class]:nth-of-type(2)')); + var ca = merchantData.pr.ca + || 'Untrusted'; + tamount.attr('class', + tamount.attr('class').replace(' hidden', '')) + tamount.html(total + ' (CA: ' + ca + + '. Expires: ' + + expires.toISOString() + + ')'); - // Reset all the changes from the payment protocol weirdness. - // XXX Bad hook. Find a better more angular-y way of doing this. - // This will also closure scope every variable above forever. - if (!scope._resetPayPro) { - scope._resetPayPro = function() { - var val = address.val(); - var uri = copay.HDPath.parseBitcoinURI(val || ''); - if (!uri || !uri.merchant) { - if (amount.attr('disabled')) { - amount.val(''); - amount.attr('disabled', false); - } - sendto.html(sendto.html().replace(/
Server:.*$/, '')); - if (!/hidden/.test(tamount.attr('class'))) { - tamount.attr(tamount.attr('class') + ' hidden'); - } - if (~tamount.html().indexOf('(CA: ')) { - tamount.html(''); - } - if (!submit.attr('disabled')) { - submit.attr('disabled', true); - } - if (/ hidden$/.test(sendall.attr('class'))) { - sendall.attr('class', - sendall.attr('class').replace(' hidden', '')); - } + var submit = angular.element( + document.querySelector('button[type=submit]')); + submit.attr('disabled', false); + + var sendall = angular.element( + document.querySelector('[title="Send all funds"]')); + sendall.attr('class', sendall.attr('class') + ' hidden'); + + // Reset all the changes from the payment protocol weirdness. + // XXX Bad hook. Find a better more angular-y way of doing this. + // This will also closure scope every variable above forever. + if (!scope._resetPayPro) { + scope._resetPayPro = function() { + var val = address.val(); + var uri = copay.HDPath.parseBitcoinURI(val || ''); + if (!uri || !uri.merchant) { + if (amount.attr('disabled')) { + amount.val(''); + amount.attr('disabled', false); } - // TODO: Check paymentRequest expiration, - // delete if beyond expiration date. - }; - scope.$watch('address',scope._resetPayPro); - } - - ctrl.$setValidity('validAddress', true); - - // XXX With PayPro, since amount is already filled in among - // other field oddities, the form is always invalid. Make it - // valid. - scope.sendForm.$valid = true; - scope.sendForm.$invalid = false; - scope.sendForm.$pristine = true; - - scope.sendForm.address.$valid = true; - scope.sendForm.address.$invalid = false; - scope.sendForm.address.$pristine = true; - - scope.sendForm.amount.$valid = true; - scope.sendForm.amount.$invalid = false; - scope.sendForm.amount.$pristine = true; - }); + sendto.html(sendto.html().replace(/
Server:.*$/, '')); + if (!/hidden/.test(tamount.attr('class'))) { + tamount.attr(tamount.attr('class') + ' hidden'); + } + if (~tamount.html().indexOf('(CA: ')) { + tamount.html(''); + } + if (!submit.attr('disabled')) { + submit.attr('disabled', true); + } + if (/ hidden$/.test(sendall.attr('class'))) { + sendall.attr('class', + sendall.attr('class').replace(' hidden', '')); + } + } + // TODO: Check paymentRequest expiration, + // delete if beyond expiration date. + }; + scope.$watch('address',scope._resetPayPro); + } ctrl.$setValidity('validAddress', true); - return 'Merchant: '+ uri.merchant; - } + // XXX With PayPro, since amount is already filled in among + // other field oddities, the form is always invalid. Make it + // valid. + scope.sendForm.$valid = true; + scope.sendForm.$invalid = false; + scope.sendForm.$pristine = true; - var a = new Address(value); - ctrl.$setValidity('validAddress', a.isValid() && a.network().name === config.networkName); - return value; + scope.sendForm.address.$valid = true; + scope.sendForm.address.$invalid = false; + scope.sendForm.address.$pristine = true; + + scope.sendForm.amount.$valid = true; + scope.sendForm.amount.$invalid = false; + scope.sendForm.amount.$pristine = true; + }); + + ctrl.$setValidity('validAddress', true); + + return 'Merchant: '+ uri.merchant; }; ctrl.$parsers.unshift(validator); From f5b1afdbf1c0b80ea7337e2355f2d6634ab90690 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Thu, 7 Aug 2014 09:59:57 -0700 Subject: [PATCH 131/178] paypro: removed a lot of now-pointless code dealing with merchantData.total. --- js/directives.js | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/js/directives.js b/js/directives.js index 21432b046..ddf24b7cb 100644 --- a/js/directives.js +++ b/js/directives.js @@ -51,20 +51,6 @@ angular.module('copayApp.directives') var payment_url = merchantData.pr.pd.payment_url; var total = merchantData.total; - if (typeof total === 'string') { - total = bignum(total, 10).toBuffer({ - endian: 'little', - size: 1 - }); - } - - total = bignum - .fromBuffer(total, { - endian: 'little', - size: 1 - }) - .toString(10); - // XXX There needs to be a better way to do this: total = +total / config.unitToSatoshi; From fe2118fcbe147e612933072d31f2e5c4c832fe51 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Thu, 7 Aug 2014 10:36:44 -0700 Subject: [PATCH 132/178] paypro: show actual notification if there are no unspent outputs for payment request. --- js/directives.js | 13 +++++-------- js/models/core/Wallet.js | 2 +- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/js/directives.js b/js/directives.js index ddf24b7cb..4d99e4d38 100644 --- a/js/directives.js +++ b/js/directives.js @@ -1,8 +1,8 @@ 'use strict'; angular.module('copayApp.directives') - .directive('validAddress', ['$rootScope', - function($rootScope) { + .directive('validAddress', ['$rootScope', 'notification', + function($rootScope, notification) { var bitcore = require('bitcore'); var Address = bitcore.Address; @@ -32,17 +32,14 @@ angular.module('copayApp.directives') var balance = $rootScope.availableBalance; var available = +(balance * config.unitToSatoshi).toFixed(0); - if ((err && err.message === 'No unspent outputs.') - || available < +merchantData.total) { - // TODO: Actually display a notification window here - // instead of simply saying the URI is invalid. - ctrl.$setValidity('validAddress', false); - return; + if (merchantData && available < +merchantData.total) { + err = new Error('No unspent outputs available.'); } if (err) { if (scope._resetPayPro) scope._resetPayPro(); ctrl.$setValidity('validAddress', false); + notification.error('Error', err.message); return; } diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index 98664efcd..cf8c9354b 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -934,7 +934,7 @@ Wallet.prototype.receivePaymentRequest = function(options, pr, cb) { return this.getUnspent(function(err, unspent) { if (options.fetch) { if (!unspent || !unspent.length) { - return cb(new Error('No unspent outputs.')); + return cb(new Error('No unspent outputs available.')); } self.createPaymentTxSync(options, merchantData, unspent); return cb(null, merchantData, pr); From 92c0b69d35b92db0d9e7c21d51866038627d37a6 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Thu, 7 Aug 2014 11:29:09 -0700 Subject: [PATCH 133/178] paypro: add more notifications. --- js/directives.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/js/directives.js b/js/directives.js index 4d99e4d38..72e9829c5 100644 --- a/js/directives.js +++ b/js/directives.js @@ -27,6 +27,9 @@ angular.module('copayApp.directives') return value; } + notification.info('Fetching Payment', + 'Retrieving Payment Request from ' + uri.merchant); + // Payment Protocol URI (BIP-72) scope.wallet.fetchPaymentTx(uri.merchant, function(err, merchantData) { var balance = $rootScope.availableBalance; @@ -51,6 +54,10 @@ angular.module('copayApp.directives') // XXX There needs to be a better way to do this: total = +total / config.unitToSatoshi; + notification.info('Payment Request', + 'Server is requesting ' + total + ' ' + config.unitName + '.\n' + + 'Message: ' + memo); + // XXX Pretty much all of this code accesses the raw DOM. It's // very bad, there's probably a better, more angular-y way to // do things here. From 6438de6bd23a4fb7e57e6e5c752cbc8768151b25 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Thu, 7 Aug 2014 11:38:26 -0700 Subject: [PATCH 134/178] paypro: more notifications. comments. misc. --- js/controllers/send.js | 14 ++++++++------ js/directives.js | 8 +++----- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/js/controllers/send.js b/js/controllers/send.js index 08a1dcac3..8ed95b34c 100644 --- a/js/controllers/send.js +++ b/js/controllers/send.js @@ -80,11 +80,11 @@ angular.module('copayApp.controllers').controller('SendController', var message = 'The transaction proposal has been created'; if (merchantData) { if (merchantData.pr.ca) { - message += '\nThis payment protocol transaction' + message += ' This payment protocol transaction' + ' has been verified through ' + merchantData.pr.ca + '.'; } - message += '\nMessage from server: ' + merchantData.ack.memo; - message += '\nFor merchant: ' + merchantData.pr.pd.payment_url; + message += ' Message from server: ' + merchantData.ack.memo; + message += ' For merchant: ' + merchantData.pr.pd.payment_url; } notification.success('Success!', message); $scope.loadTxs(); @@ -94,11 +94,11 @@ angular.module('copayApp.controllers').controller('SendController', var message = 'Transaction id: ' + txid; if (merchantData) { if (merchantData.pr.ca) { - message += '\nThis payment protocol transaction' + message += ' This payment protocol transaction' + ' has been verified through ' + merchantData.pr.ca + '.'; } - message += '\nMessage from server: ' + merchantData.ack.memo; - message += '\nFor merchant: ' + merchantData.pr.pd.payment_url; + message += ' Message from server: ' + merchantData.ack.memo; + message += ' For merchant: ' + merchantData.pr.pd.payment_url; } notification.success('Transaction broadcast', message); } else { @@ -116,6 +116,8 @@ angular.module('copayApp.controllers').controller('SendController', uri = copay.HDPath.parseBitcoinURI(address); } else if (address.indexOf('Merchant: ') === 0) { uri = { merchant: address.split(/\s+/)[1] }; + } else if (/^https?:\/\//.test(address)) { + uri = { merchant: address }; } if (uri && uri.merchant) { diff --git a/js/directives.js b/js/directives.js index 72e9829c5..cefd996a5 100644 --- a/js/directives.js +++ b/js/directives.js @@ -55,8 +55,8 @@ angular.module('copayApp.directives') total = +total / config.unitToSatoshi; notification.info('Payment Request', - 'Server is requesting ' + total + ' ' + config.unitName + '.\n' - + 'Message: ' + memo); + 'Server is requesting ' + total + ' ' + config.unitName + '.' + + ' Message: ' + memo); // XXX Pretty much all of this code accesses the raw DOM. It's // very bad, there's probably a better, more angular-y way to @@ -122,8 +122,6 @@ angular.module('copayApp.directives') sendall.attr('class').replace(' hidden', '')); } } - // TODO: Check paymentRequest expiration, - // delete if beyond expiration date. }; scope.$watch('address',scope._resetPayPro); } @@ -148,7 +146,7 @@ angular.module('copayApp.directives') ctrl.$setValidity('validAddress', true); - return 'Merchant: '+ uri.merchant; + return 'Merchant: ' + uri.merchant; }; ctrl.$parsers.unshift(validator); From 2c0ffa4eea595df00a7088ff11c45e64db218a87 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Thu, 7 Aug 2014 11:41:02 -0700 Subject: [PATCH 135/178] package.json: use latest HEAD for bitcore. --- bower.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bower.json b/bower.json index d96cd64d8..f38b3fc68 100644 --- a/bower.json +++ b/bower.json @@ -18,7 +18,7 @@ "sjcl": "1.0.0", "file-saver": "*", "qrcode-decoder-js": "*", - "bitcore": "0.1.34", + "bitcore": "git://github.com/bitpay/bitcore.git#master", "angular-moment": "~0.7.1", "socket.io-client": ">=1.0.0", "mousetrap": "1.4.6", diff --git a/package.json b/package.json index 6504fb200..0d804e198 100644 --- a/package.json +++ b/package.json @@ -48,7 +48,7 @@ "github-releases": "0.2.0", "grunt-markdown": "0.5.0", "browser-pack": "2.0.1", - "bitcore": "0.1.34", + "bitcore": "git://github.com/bitpay/bitcore.git#master", "node-cryptojs-aes": "0.4.0", "blanket": "1.1.6", "express": "4.0.0", From 86a725240b8a235d2d395bab879ee566b2a55384 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Thu, 7 Aug 2014 15:15:02 -0700 Subject: [PATCH 136/178] paypro: minor - fix notification text. --- js/controllers/send.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/js/controllers/send.js b/js/controllers/send.js index 8ed95b34c..96eb79c11 100644 --- a/js/controllers/send.js +++ b/js/controllers/send.js @@ -356,11 +356,11 @@ angular.module('copayApp.controllers').controller('SendController', } else { var message = 'Transaction ID: ' + txid; if (merchantData.pr.ca) { - message += '\nThis payment protocol transaction' + message += ' This payment protocol transaction' + ' has been verified through ' + merchantData.pr.ca + '.'; } - message += '\nMessage from server: ' + merchantData.ack.memo; - message += '\nFor merchant: ' + merchantData.pr.pd.payment_url; + message += ' Message from server: ' + merchantData.ack.memo; + message += ' For merchant: ' + merchantData.pr.pd.payment_url; notification.success('Transaction sent', message); } } From aed815f5cdfcf63bd4711344f7b3b30d0b2dbc25 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Fri, 8 Aug 2014 12:51:50 -0700 Subject: [PATCH 137/178] paypro: angularize the markup and address directive. --- js/directives.js | 135 ++++++++++++++--------------------------------- views/send.html | 22 +++++--- 2 files changed, 55 insertions(+), 102 deletions(-) diff --git a/js/directives.js b/js/directives.js index cefd996a5..343eff2e2 100644 --- a/js/directives.js +++ b/js/directives.js @@ -40,108 +40,51 @@ angular.module('copayApp.directives') } if (err) { - if (scope._resetPayPro) scope._resetPayPro(); - ctrl.$setValidity('validAddress', false); - notification.error('Error', err.message); + scope.sendForm.address.$isValid = false; + notification.error('Error', err.message || 'Bad payment server.'); return; } - var expires = new Date(merchantData.pr.pd.expires * 1000); - var memo = merchantData.pr.pd.memo; - var payment_url = merchantData.pr.pd.payment_url; - var total = merchantData.total; + merchantData.unitTotal = (+merchantData.total / config.unitToSatoshi) + ''; + merchantData.expiration = new Date( + merchantData.pr.pd.expires * 1000).toISOString(); - // XXX There needs to be a better way to do this: - total = +total / config.unitToSatoshi; + $rootScope.merchant = merchantData; + + scope.sendForm.address.$isValid = true; + + scope.sendForm.amount.$setViewValue(merchantData.unitTotal); + scope.sendForm.amount.$render(); + scope.sendForm.amount.$isValid = true; + + // If the address changes to a non-payment-protocol one, + // delete the `merchant` property from the scope. + var unregister = scope.$watch('address', function() { + var val = scope.sendForm.address.$viewValue || ''; + var uri = copay.HDPath.parseBitcoinURI(val); + if (!uri || !uri.merchant) { + delete $rootScope.merchant; + scope.sendForm.amount.$setViewValue(''); + scope.sendForm.amount.$render(); + unregister(); + if ($rootScope.$$phase !== '$apply' + && $rootScope.$$phase !== '$digest') { + $rootScope.$apply(); + } + } + }); + + if ($rootScope.$$phase !== '$apply' + && $rootScope.$$phase !== '$digest') { + $rootScope.$apply(); + } notification.info('Payment Request', - 'Server is requesting ' + total + ' ' + config.unitName + '.' - + ' Message: ' + memo); - - // XXX Pretty much all of this code accesses the raw DOM. It's - // very bad, there's probably a better, more angular-y way to - // do things here. - - var address = angular.element( - document.querySelector('input#address')); - - var amount = angular.element( - document.querySelector('input#amount')); - amount.val(total); - if (+merchantData.total !== 0) { - amount.attr('disabled', true); - } - - var sendto = angular.element(document - .querySelector('div.send-note > p[ng-class]:first-of-type')); - sendto.html(sendto.html() + '
Server: ' + memo); - - var tamount = angular.element(document - .querySelector('div.send-note > p[ng-class]:nth-of-type(2)')); - var ca = merchantData.pr.ca - || 'Untrusted'; - tamount.attr('class', - tamount.attr('class').replace(' hidden', '')) - tamount.html(total + ' (CA: ' + ca - + '. Expires: ' - + expires.toISOString() - + ')'); - - var submit = angular.element( - document.querySelector('button[type=submit]')); - submit.attr('disabled', false); - - var sendall = angular.element( - document.querySelector('[title="Send all funds"]')); - sendall.attr('class', sendall.attr('class') + ' hidden'); - - // Reset all the changes from the payment protocol weirdness. - // XXX Bad hook. Find a better more angular-y way of doing this. - // This will also closure scope every variable above forever. - if (!scope._resetPayPro) { - scope._resetPayPro = function() { - var val = address.val(); - var uri = copay.HDPath.parseBitcoinURI(val || ''); - if (!uri || !uri.merchant) { - if (amount.attr('disabled')) { - amount.val(''); - amount.attr('disabled', false); - } - sendto.html(sendto.html().replace(/
Server:.*$/, '')); - if (!/hidden/.test(tamount.attr('class'))) { - tamount.attr(tamount.attr('class') + ' hidden'); - } - if (~tamount.html().indexOf('(CA: ')) { - tamount.html(''); - } - if (!submit.attr('disabled')) { - submit.attr('disabled', true); - } - if (/ hidden$/.test(sendall.attr('class'))) { - sendall.attr('class', - sendall.attr('class').replace(' hidden', '')); - } - } - }; - scope.$watch('address',scope._resetPayPro); - } - - ctrl.$setValidity('validAddress', true); - - // XXX With PayPro, since amount is already filled in among - // other field oddities, the form is always invalid. Make it - // valid. - scope.sendForm.$valid = true; - scope.sendForm.$invalid = false; - scope.sendForm.$pristine = true; - - scope.sendForm.address.$valid = true; - scope.sendForm.address.$invalid = false; - scope.sendForm.address.$pristine = true; - - scope.sendForm.amount.$valid = true; - scope.sendForm.amount.$invalid = false; - scope.sendForm.amount.$pristine = true; + 'Server is requesting ' + + merchantData.unitTotal + ' ' + + config.unitName + '.' + + ' Message: ' + + merchantData.pr.pd.memo); }); ctrl.$setValidity('validAddress', true); diff --git a/views/send.html b/views/send.html index a3c719162..ec7f0ce89 100644 --- a/views/send.html +++ b/views/send.html @@ -64,7 +64,8 @@ Insufficient funds
{{$root.unitName}} @@ -114,15 +115,24 @@
Send to

{{address}}  + Server: {{$root.merchant.pr.pd.memo}}

Total amount for this transaction:
-

+

{{amount + defaultFee |noFractionNumber}} {{$root.unitName}} {{ ((amount + defaultFee) * unitToBtc)|noFractionNumber:8}} BTC
Including fee of {{defaultFee|noFractionNumber}} {{$root.unitName}}

+

+ {{amount + defaultFee | noFractionNumber}} {{$root.unitName}}
+ CA: + {{$root.merchant.pr.ca}} + Untrusted +
+ Expires: {{$root.merchant.expiration}} +

Note

{{commentText}}

@@ -144,8 +154,8 @@ - {{info.label}} {{addr}} From 9d94fa0b75a8061012d97d944ace2f89475bd301 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Fri, 8 Aug 2014 13:37:06 -0700 Subject: [PATCH 138/178] paypro: remove old comments and logs from tests. --- test/test.PayPro.js | 60 --------------------------------------------- 1 file changed, 60 deletions(-) diff --git a/test/test.PayPro.js b/test/test.PayPro.js index 3afc15c6f..1d4745646 100644 --- a/test/test.PayPro.js +++ b/test/test.PayPro.js @@ -252,17 +252,6 @@ describe('PayPro (in Wallet) model', function() { output.get('script').buffer)) }; - // little endian - // var v = new Buffer(8); - // v[0] = (amount.low >> 0) & 0xff; - // v[1] = (amount.low >> 8) & 0xff; - // v[2] = (amount.low >> 16) & 0xff; - // v[3] = (amount.low >> 24) & 0xff; - // v[4] = (amount.high >> 0) & 0xff; - // v[5] = (amount.high >> 8) & 0xff; - // v[6] = (amount.high >> 16) & 0xff; - // v[7] = (amount.high >> 24) & 0xff; - // big endian var v = new Buffer(8); v[0] = (amount.high >> 24) & 0xff; @@ -283,15 +272,10 @@ describe('PayPro (in Wallet) model', function() { amountSatStr: bitcore.Bignum.fromBuffer(v, { // XXX for some reason, endian is ALWAYS 'big' // in node (in the browser it behaves correctly) - // endian: 'little', endian: 'big', size: 1 }).toString(10) }); - - console.log('Output 1:'); - console.log('Buffer: ' + v.toString('hex')); - console.log(outs[outs.length - 1]); }); var b = new bitcore.TransactionBuilder(opts) @@ -336,13 +320,6 @@ describe('PayPro (in Wallet) model', function() { endian: 'big', size: 1 })); - - // XXX potential problem: bignum seems bugged in node - tx outputs use - // little endian, but fromBuffer(endian=little) ends up being big endian - // return total.add(bitcore.Bignum.fromBuffer(tx.outs[i].v, { - // endian: 'little', - // size: 1 - // })); }, bitcore.Bignum('0', 10)); var rpo = new PayPro(); @@ -435,13 +412,6 @@ describe('PayPro (in Wallet) model', function() { endian: 'big', size: 1 })); - - // XXX potential problem: bignum seems bugged in node - tx outputs use - // little endian, but fromBuffer(endian=little) ends up being big endian - // return total.add(bitcore.Bignum.fromBuffer(tx.outs[i].v, { - // endian: 'little', - // size: 1 - // })); }, bitcore.Bignum('0', 10)); ackTotal.toString(10).should.equal(total.toString(10)); @@ -531,17 +501,6 @@ describe('PayPro (in Wallet) model', function() { output.get('script').buffer)) }; - // little endian - // var v = new Buffer(8); - // v[0] = (amount.low >> 0) & 0xff; - // v[1] = (amount.low >> 8) & 0xff; - // v[2] = (amount.low >> 16) & 0xff; - // v[3] = (amount.low >> 24) & 0xff; - // v[4] = (amount.high >> 0) & 0xff; - // v[5] = (amount.high >> 8) & 0xff; - // v[6] = (amount.high >> 16) & 0xff; - // v[7] = (amount.high >> 24) & 0xff; - // big endian var v = new Buffer(8); v[0] = (amount.high >> 24) & 0xff; @@ -562,15 +521,10 @@ describe('PayPro (in Wallet) model', function() { amountSatStr: bitcore.Bignum.fromBuffer(v, { // XXX for some reason, endian is ALWAYS 'big' // in node (in the browser it behaves correctly) - // endian: 'little', endian: 'big', size: 1 }).toString(10) }); - - console.log('Output 2:'); - console.log('Buffer: ' + v.toString('hex')); - console.log(outs[outs.length - 1]); }); var b = new bitcore.TransactionBuilder(opts) @@ -615,13 +569,6 @@ describe('PayPro (in Wallet) model', function() { endian: 'big', size: 1 })); - - // XXX potential problem: bignum seems bugged in node - tx outputs use - // little endian, but fromBuffer(endian=little) ends up being big endian - // return total.add(bitcore.Bignum.fromBuffer(tx.outs[i].v, { - // endian: 'little', - // size: 1 - // })); }, bitcore.Bignum('0', 10)); var rpo = new PayPro(); @@ -714,13 +661,6 @@ describe('PayPro (in Wallet) model', function() { endian: 'big', size: 1 })); - - // XXX potential problem: bignum seems bugged in node - tx outputs use - // little endian, but fromBuffer(endian=little) ends up being big endian - // return total.add(bitcore.Bignum.fromBuffer(tx.outs[i].v, { - // endian: 'little', - // size: 1 - // })); }, bitcore.Bignum('0', 10)); ackTotal.toString(10).should.equal(total.toString(10)); From d4d33ed2e725d9a14b0642629abfe90c08224913 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Fri, 8 Aug 2014 13:37:25 -0700 Subject: [PATCH 139/178] paypro: update tests to use wallet.isShared(). --- test/test.PayPro.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/test.PayPro.js b/test/test.PayPro.js index 1d4745646..d25a84d13 100644 --- a/test/test.PayPro.js +++ b/test/test.PayPro.js @@ -728,7 +728,7 @@ describe('PayPro (in Wallet) model', function() { }, function(ntxid, merchantData) { should.exist(ntxid); should.exist(merchantData); - if (w.totalCopayers > 1) { + if (w.isShared()) { return done(); } else { w.sendPaymentTx(ntxid, { memo: memo }, function(txid, merchantData) { @@ -755,7 +755,7 @@ describe('PayPro (in Wallet) model', function() { } w.createTx(uri, commentText, function(ntxid, merchantData) { - if (w.totalCopayers > 1) { + if (w.isShared()) { should.exist(ntxid); should.exist(merchantData); return done(); @@ -776,7 +776,7 @@ describe('PayPro (in Wallet) model', function() { 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.totalCopayers > 1) { + if (w.isShared()) { should.exist(ntxid); should.exist(merchantData); return done(); From 361b885d7156845f86de28c8845010abfdc36341 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Fri, 8 Aug 2014 13:41:27 -0700 Subject: [PATCH 140/178] paypro: remove uneccessary if clause in parseBitcoinURI. --- js/models/core/HDPath.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/js/models/core/HDPath.js b/js/models/core/HDPath.js index 12a7c0f73..7b0be32e8 100644 --- a/js/models/core/HDPath.js +++ b/js/models/core/HDPath.js @@ -67,11 +67,7 @@ HDPath.parseBitcoinURI = function(uri) { var i = 0; for (; i < parts.length; i++) { part = parts[i].split('='); - if (part[0] === '') { - data[part[1]] = part[1]; - } else { - data[part[0]] = decodeURIComponent(part[1]); - } + data[part[0]] = decodeURIComponent(part[1]); } ret.amount = parseFloat(data.amount); ret.message = data.message; From a1619e3f6c71f673fa1afd337a7c95257df6e8bd Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Fri, 8 Aug 2014 13:49:31 -0700 Subject: [PATCH 141/178] paypro: minor - remove comment. --- js/models/core/Wallet.js | 7 ------- 1 file changed, 7 deletions(-) diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index cf8c9354b..9cbe857e5 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -987,13 +987,6 @@ Wallet.prototype.sendPaymentTx = function(ntxid, options, cb) { endian: 'big', size: 1 })); - - // XXX potential problem: bignum seems bugged in node - tx outputs use - // little endian, but fromBuffer(endian=little) ends up being big endian - // return total.add(bignum.fromBuffer(tx.outs[i].v, { - // endian: 'little', - // size: 1 - // })); }, bignum('0', 10)); var rpo = new PayPro(); From 4159c5aa5b4ea452c1261ce285d55fc7d192b419 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Fri, 8 Aug 2014 14:10:18 -0700 Subject: [PATCH 142/178] paypro: fix sendPaymentTx to be in line with sendTx. --- js/models/core/Wallet.js | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index 9cbe857e5..804742fc9 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -1062,14 +1062,14 @@ Wallet.prototype.sendPaymentTx = function(ntxid, options, cb) { data = PayPro.PaymentACK.decode(data); var ack = new PayPro(); ack = ack.makePaymentACK(data); - return self.receivePaymentRequestACK(tx, txp, ack, cb); + return self.receivePaymentRequestACK(ntxid, tx, txp, ack, cb); }) .error(function(data, status, headers, config) { return cb(new Error('Status: ' + JSON.stringify(status))); }); }; -Wallet.prototype.receivePaymentRequestACK = function(tx, txp, ack, cb) { +Wallet.prototype.receivePaymentRequestACK = function(ntxid, tx, txp, ack, cb) { var self = this; var payment = ack.get('payment'); @@ -1082,10 +1082,19 @@ Wallet.prototype.receivePaymentRequestACK = function(tx, txp, ack, cb) { var pay = new PayPro(); payment = pay.makePayment(payment); + txp.merchant.ack = { + memo: memo + }; + var tx = payment.message.transactions[0]; if (!tx) { - return cb(); + this.log('Sending to server was not met with a returned tx.'); + return this._checkSentTx(ntxid, function(txid) { + self.log('[Wallet.js.1048:txid:%s]', txid); + if (txid) self.store(); + return cb(txid, txp.merchant); + }); } if (tx.buffer) { @@ -1096,11 +1105,14 @@ Wallet.prototype.receivePaymentRequestACK = function(tx, txp, ack, cb) { tx = ptx; } - txp.merchant.ack = { - memo: memo - }; - var txid = tx.getHash().toString('hex'); + var txHex = tx.serialize().toString('hex'); + this.log('Raw transaction: ', txHex); + this.log('BITCOIND txid:', txid); + this.txProposals.get(ntxid).setSent(txid); + this.sendTxProposal(ntxid); + this.store(); + return cb(txid, txp.merchant); }; From 5ebb344cd293ab2e9d03b6014c6d16825f46ae5d Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Fri, 8 Aug 2014 14:28:00 -0700 Subject: [PATCH 143/178] send: lower btc send limit - dust. --- views/send.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/views/send.html b/views/send.html index ec7f0ce89..ef6fabe46 100644 --- a/views/send.html +++ b/views/send.html @@ -67,7 +67,7 @@ From fcb048482836112d377da91e261b013eb2c1f1d1 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Mon, 11 Aug 2014 15:32:02 -0400 Subject: [PATCH 144/178] paypro: ui - move merchant data into its own element. --- views/send.html | 34 ++++++++++++++++++++++------------ 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/views/send.html b/views/send.html index ef6fabe46..037d4eb45 100644 --- a/views/send.html +++ b/views/send.html @@ -115,28 +115,38 @@
Send to

{{address}}  - Server: {{$root.merchant.pr.pd.memo}}

Total amount for this transaction:
-

+

{{amount + defaultFee |noFractionNumber}} {{$root.unitName}} {{ ((amount + defaultFee) * unitToBtc)|noFractionNumber:8}} BTC
Including fee of {{defaultFee|noFractionNumber}} {{$root.unitName}}

-

- {{amount + defaultFee | noFractionNumber}} {{$root.unitName}}
- CA: +

+
Note
+

{{commentText}}

+
+
+
Merchant Data:
+

+ Note: This is a payment protocol transaction. +

+
Server Says:
+

+ {{$root.merchant.pr.pd.memo}} +

+
Certificate:
+

{{$root.merchant.pr.ca}} Untrusted -
- Expires: {{$root.merchant.expiration}} -

-
-
Note
-

{{commentText}}

-
+

+
Payment Expiration:
+

+ {{$root.merchant.expiration}} +

+
From 05859b77d62bcd3cebe8250fa4a07985006f0c4e Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Mon, 11 Aug 2014 18:33:52 -0400 Subject: [PATCH 145/178] paypro: test - remove old function. --- test/test.PayPro.js | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/test/test.PayPro.js b/test/test.PayPro.js index d25a84d13..d31933c69 100644 --- a/test/test.PayPro.js +++ b/test/test.PayPro.js @@ -93,18 +93,6 @@ describe('PayPro (in Wallet) model', function() { return new Wallet(c); } - var cachedW = null; - var cachedWobj = null; - var cachedCreateW = function() { - if (!cachedW) { - cachedW = createW(); - cachedWobj = cachedW.toObj(); - cachedWobj.opts.reconnectDelay = 100; - } - var w = Wallet.fromObj(cachedWobj, cachedW.storage, cachedW.network, cachedW.blockchain); - return w; - }; - var unspentTest = [{ "address": "dummy", "scriptPubKey": "dummy", From 086e2f65725e02154bebe8f9281b68a93b5b7de7 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Mon, 11 Aug 2014 18:37:48 -0400 Subject: [PATCH 146/178] paypro: fix accept header for bitpay.com. --- js/models/core/Wallet.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index 804742fc9..b8fa3ec13 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -804,8 +804,7 @@ Wallet.prototype.createPaymentTx = function(options, cb) { method: options.method || 'POST', url: options.uri, headers: { - 'Accept': PayPro.PAYMENT_REQUEST_CONTENT_TYPE - + ', ' + PayPro.PAYMENT_ACK_CONTENT_TYPE, + 'Accept': PayPro.PAYMENT_REQUEST_CONTENT_TYPE, 'Content-Type': 'application/octet-stream' // XHR does not allow this: // 'Content-Length': '0' @@ -1046,8 +1045,7 @@ Wallet.prototype.sendPaymentTx = function(ntxid, options, cb) { url: txp.merchant.pr.pd.payment_url, headers: { // BIP-71 - 'Accept': PayPro.PAYMENT_REQUEST_CONTENT_TYPE - + ', ' + PayPro.PAYMENT_ACK_CONTENT_TYPE, + 'Accept': PayPro.PAYMENT_ACK_CONTENT_TYPE, 'Content-Type': PayPro.PAYMENT_CONTENT_TYPE // XHR does not allow these: // 'Content-Length': (pay.byteLength || pay.length) + '', From 61f20605f58b7537e5b93deea2bddfcd11eb97e9 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Mon, 11 Aug 2014 18:38:43 -0400 Subject: [PATCH 147/178] paypro: minor - fix test messages for unit controller tests. --- test/unit/controllers/controllersSpec.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/unit/controllers/controllersSpec.js b/test/unit/controllers/controllersSpec.js index 018c8e171..6945d30a9 100644 --- a/test/unit/controllers/controllersSpec.js +++ b/test/unit/controllers/controllersSpec.js @@ -229,7 +229,7 @@ describe("Unit: Controllers", function() { }); }); - it('should create a payment protocol transaction proposal', function() { + it('#create a payment protocol transaction proposal', function() { var uri = 'bitcoin:1JqniWpWNA6Yvdivg3y9izLidETnurxRQm?amount=0.00001000&r=https://localhost:8080/-/request'; sendForm.address.$setViewValue(uri); sendForm.amount.$setViewValue(1000); @@ -242,7 +242,7 @@ describe("Unit: Controllers", function() { sinon.assert.callCount(spy2, 0); }); - it('should create and send a payment protocol transaction proposal', function() { + it('#create and send a payment protocol transaction proposal', function() { var uri = 'bitcoin:1JqniWpWNA6Yvdivg3y9izLidETnurxRQm?amount=0.00001000&r=https://localhost:8080/-/request'; sendForm.address.$setViewValue(uri); sendForm.amount.$setViewValue(1000); From 08f1a4925994b1a7c2260e27b0a7125acae82a59 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Mon, 11 Aug 2014 18:39:04 -0400 Subject: [PATCH 148/178] paypro: remove useless header from http request. --- js/models/core/Wallet.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index b8fa3ec13..1c2e2ec95 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -804,10 +804,7 @@ Wallet.prototype.createPaymentTx = function(options, cb) { method: options.method || 'POST', url: options.uri, headers: { - 'Accept': PayPro.PAYMENT_REQUEST_CONTENT_TYPE, - 'Content-Type': 'application/octet-stream' - // XHR does not allow this: - // 'Content-Length': '0' + 'Accept': PayPro.PAYMENT_REQUEST_CONTENT_TYPE }, responseType: 'arraybuffer' }) From efe41cead49cfb2de588785402b800cd0e08fdec Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Mon, 11 Aug 2014 19:03:45 -0400 Subject: [PATCH 149/178] paypro: use unspecified GET method for first paypro request. --- js/models/core/Wallet.js | 2 +- test/mocks/FakePayProServer.js | 2 +- test/test.PayPro.js | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index 1c2e2ec95..71153f565 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -801,7 +801,7 @@ Wallet.prototype.createPaymentTx = function(options, cb) { } return Wallet.request({ - method: options.method || 'POST', + method: 'GET', url: options.uri, headers: { 'Accept': PayPro.PAYMENT_REQUEST_CONTENT_TYPE diff --git a/test/mocks/FakePayProServer.js b/test/mocks/FakePayProServer.js index 6f0f76138..0b6002606 100644 --- a/test/mocks/FakePayProServer.js +++ b/test/mocks/FakePayProServer.js @@ -101,7 +101,7 @@ function startServer(cb) { var old = Wallet.request; var server = { - POST: { + GET: { /** * Receive "I want to pay" diff --git a/test/test.PayPro.js b/test/test.PayPro.js index d31933c69..288e68762 100644 --- a/test/test.PayPro.js +++ b/test/test.PayPro.js @@ -182,7 +182,7 @@ describe('PayPro (in Wallet) model', function() { req.headers[key.toLowerCase()] = req.headers[key]; }); - server.POST['/-/request'](req, function(err, res, body) { + server.GET['/-/request'](req, function(err, res, body) { var data = PayPro.PaymentRequest.decode(body); pr = new PayPro(); pr = pr.makePaymentRequest(data); @@ -431,7 +431,7 @@ describe('PayPro (in Wallet) model', function() { req.headers[key.toLowerCase()] = req.headers[key]; }); - server.POST['/-/request'](req, function(err, res, body) { + server.GET['/-/request'](req, function(err, res, body) { var data = PayPro.PaymentRequest.decode(body); pr = new PayPro(); pr = pr.makePaymentRequest(data); From 6ec8a087f3f3b6eebe5c84898ea411d72363cdf9 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Mon, 11 Aug 2014 19:23:13 -0400 Subject: [PATCH 150/178] paypro: fix POST route in fake paypro server. --- test/mocks/FakePayProServer.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/mocks/FakePayProServer.js b/test/mocks/FakePayProServer.js index 0b6002606..e854fb656 100644 --- a/test/mocks/FakePayProServer.js +++ b/test/mocks/FakePayProServer.js @@ -210,11 +210,13 @@ function startServer(cb) { return cb(null, res, res.body); }, + }, /** * Receive Payment */ + POST: { '/-/pay': function(req, cb) { var body = req.body; From 8d2393230e08239ffa4ca2c04989991d18809631 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Wed, 13 Aug 2014 13:35:33 -0400 Subject: [PATCH 151/178] paypro: allow specific paypro headers. --- js/models/core/Wallet.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index 71153f565..ff5db4ebd 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -804,6 +804,7 @@ Wallet.prototype.createPaymentTx = function(options, cb) { method: 'GET', url: options.uri, headers: { + 'Access-Control-Request-Headers': 'Content-Type,Content-Length,Accept', 'Accept': PayPro.PAYMENT_REQUEST_CONTENT_TYPE }, responseType: 'arraybuffer' @@ -1042,6 +1043,7 @@ Wallet.prototype.sendPaymentTx = function(ntxid, options, cb) { url: txp.merchant.pr.pd.payment_url, headers: { // BIP-71 + 'Access-Control-Request-Headers': 'Content-Type,Content-Length,Accept', 'Accept': PayPro.PAYMENT_ACK_CONTENT_TYPE, 'Content-Type': PayPro.PAYMENT_CONTENT_TYPE // XHR does not allow these: From 32971772d1e3c4bd3821b846fca98cdfcb4260e6 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Wed, 13 Aug 2014 13:35:48 -0400 Subject: [PATCH 152/178] Revert "paypro: allow specific paypro headers." This reverts commit bec2df549b5565b3f846e9a59e5240db01877d88. --- js/models/core/Wallet.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index ff5db4ebd..71153f565 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -804,7 +804,6 @@ Wallet.prototype.createPaymentTx = function(options, cb) { method: 'GET', url: options.uri, headers: { - 'Access-Control-Request-Headers': 'Content-Type,Content-Length,Accept', 'Accept': PayPro.PAYMENT_REQUEST_CONTENT_TYPE }, responseType: 'arraybuffer' @@ -1043,7 +1042,6 @@ Wallet.prototype.sendPaymentTx = function(ntxid, options, cb) { url: txp.merchant.pr.pd.payment_url, headers: { // BIP-71 - 'Access-Control-Request-Headers': 'Content-Type,Content-Length,Accept', 'Accept': PayPro.PAYMENT_ACK_CONTENT_TYPE, 'Content-Type': PayPro.PAYMENT_CONTENT_TYPE // XHR does not allow these: From 1a186b46408f8ebbc1edc2d82b448677e31d2fec Mon Sep 17 00:00:00 2001 From: Manuel Araoz Date: Mon, 11 Aug 2014 17:08:18 -0400 Subject: [PATCH 153/178] fix requires for karma --- karma.conf.js | 1 + test/mocks/FakePayProServer.js | 1 + util/build.js | 12 +++++++++--- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/karma.conf.js b/karma.conf.js index fd2c586fe..7211a9f15 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -54,6 +54,7 @@ module.exports = function(config) { 'test/lib/chai-should.js', 'test/lib/chai-expect.js', 'test/mocks/FakeWallet.js', + 'test/mocks/FakePayProServer.js', 'test/mocha.conf.js', diff --git a/test/mocks/FakePayProServer.js b/test/mocks/FakePayProServer.js index e854fb656..e1e507201 100644 --- a/test/mocks/FakePayProServer.js +++ b/test/mocks/FakePayProServer.js @@ -3,6 +3,7 @@ var is_browser = typeof process == 'undefined' || typeof process.versions === 'undefined'; var bitcore = bitcore || require('bitcore'); +var Buffer = bitcore.Buffer; var PayPro = bitcore.PayPro; var Wallet = require('../../js/models/core/Wallet'); diff --git a/util/build.js b/util/build.js index 4ac9317ac..d8a41e3cf 100644 --- a/util/build.js +++ b/util/build.js @@ -60,18 +60,24 @@ var createBundle = function(opts) { b.require('./js/models/core/Wallet', { expose: '../js/models/core/Wallet' }); + b.require('./js/models/core/Wallet', { + expose: '../../js/models/core/Wallet' + }); b.require('./test/mocks/FakeStorage', { expose: './mocks/FakeStorage' }); + b.require('./test/mocks/FakePayProServer', { + expose: './mocks/FakePayProServer' + }); + b.require('./test/mocks/FakePayProServer', { + expose: '../../mocks/FakePayProServer' + }); b.require('./test/mocks/FakeBlockchain', { expose: './mocks/FakeBlockchain' }); b.require('./test/mocks/FakeLocalStorage', { expose: './mocks/FakeLocalStorage' }); - b.require('./js/models/core/Wallet', { - expose: '../js/models/core/Wallet' - }); b.require('./test/mocks/FakeNetwork', { expose: './mocks/FakeNetwork' }); From e2ab9dd0c8b40b55783f6790cf96d8d9e147598c Mon Sep 17 00:00:00 2001 From: Manuel Araoz Date: Mon, 11 Aug 2014 17:44:35 -0400 Subject: [PATCH 154/178] fix one test --- test/mocks/FakeWallet.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/test/mocks/FakeWallet.js b/test/mocks/FakeWallet.js index 48b4af4a6..136dd95c5 100644 --- a/test/mocks/FakeWallet.js +++ b/test/mocks/FakeWallet.js @@ -53,7 +53,17 @@ FakeWallet.prototype.isShared = function() { FakeWallet.prototype.isReady = function() { return true; -} +}; + +FakeWallet.prototype.fetchPaymentTx = function() { + +}; + + +FakeWallet.prototype.createPaymentTx = function() { + +}; + FakeWallet.prototype.getBalance = function(cb) { From d43e7943d39efb508067268f8093d06a11d75346 Mon Sep 17 00:00:00 2001 From: Manuel Araoz Date: Mon, 11 Aug 2014 18:00:19 -0400 Subject: [PATCH 155/178] move towards fixing karma tests --- test/mocks/FakeWallet.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/test/mocks/FakeWallet.js b/test/mocks/FakeWallet.js index 136dd95c5..afb587113 100644 --- a/test/mocks/FakeWallet.js +++ b/test/mocks/FakeWallet.js @@ -55,8 +55,14 @@ FakeWallet.prototype.isReady = function() { return true; }; -FakeWallet.prototype.fetchPaymentTx = function() { - +FakeWallet.prototype.fetchPaymentTx = function(opts, cb) { + cb(null, { + pr: { + pd: { + expires: 12 + } + } + }); }; From 10bd5ba6bf3adbc9cacbb76274d5fc7e49cf8a43 Mon Sep 17 00:00:00 2001 From: Manuel Araoz Date: Mon, 11 Aug 2014 19:29:44 -0400 Subject: [PATCH 156/178] moving torwards fixing tests --- test/mocks/FakeWallet.js | 8 ++++---- test/unit/controllers/controllersSpec.js | 1 + 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/test/mocks/FakeWallet.js b/test/mocks/FakeWallet.js index afb587113..d5eceef10 100644 --- a/test/mocks/FakeWallet.js +++ b/test/mocks/FakeWallet.js @@ -1,3 +1,6 @@ +var Wallet = require('../../js/models/core/Wallet'); + + var FakeWallet = function() { this.id = 'testID'; this.balance = 10000; @@ -66,10 +69,7 @@ FakeWallet.prototype.fetchPaymentTx = function(opts, cb) { }; -FakeWallet.prototype.createPaymentTx = function() { - -}; - +FakeWallet.prototype.createPaymentTx = Wallet.prototype.createPaymentTx; FakeWallet.prototype.getBalance = function(cb) { diff --git a/test/unit/controllers/controllersSpec.js b/test/unit/controllers/controllersSpec.js index 6945d30a9..3e08db32a 100644 --- a/test/unit/controllers/controllersSpec.js +++ b/test/unit/controllers/controllersSpec.js @@ -153,6 +153,7 @@ describe("Unit: Controllers", function() { scope.$digest(); form = scope.form; sendForm = scope.form2; + scope.sendForm = sendForm; })); it('should have a SendController controller', function() { From 0067ab4da642c5eebbd589a602fb7328dd9c4411 Mon Sep 17 00:00:00 2001 From: Manuel Araoz Date: Wed, 13 Aug 2014 18:07:57 -0400 Subject: [PATCH 157/178] fix some karma tests problems --- js/directives.js | 201 +++++++++++------------ test/unit/controllers/controllersSpec.js | 41 ----- 2 files changed, 99 insertions(+), 143 deletions(-) diff --git a/js/directives.js b/js/directives.js index 343eff2e2..145366b65 100644 --- a/js/directives.js +++ b/js/directives.js @@ -1,103 +1,96 @@ 'use strict'; angular.module('copayApp.directives') - .directive('validAddress', ['$rootScope', 'notification', - function($rootScope, notification) { + .directive('validAddress', function($rootScope, notification) { + var bitcore = require('bitcore'); + var Address = bitcore.Address; + var bignum = bitcore.Bignum; - var bitcore = require('bitcore'); - var Address = bitcore.Address; - var bignum = bitcore.Bignum; + return { + require: 'ngModel', + link: function(scope, elem, attrs, ctrl) { + var validator = function(value) { + var uri; - return { - require: 'ngModel', - link: function(scope, elem, attrs, ctrl) { - var validator = function(value) { - var uri; + if (/^https?:\/\//.test(value)) { + uri = { + merchant: value + }; + } else { + uri = copay.HDPath.parseBitcoinURI(value); + } - if (/^https?:\/\//.test(value)) { - uri = { merchant: value }; - } else { - uri = copay.HDPath.parseBitcoinURI(value); + // Regular Address + if (!uri || !uri.merchant) { + var a = new Address(value); + ctrl.$setValidity('validAddress', a.isValid() && a.network().name === config.networkName); + return value; + } + + notification.info('Fetching Payment', + 'Retrieving Payment Request from ' + uri.merchant); + + // Payment Protocol URI (BIP-72) + scope.wallet.fetchPaymentTx(uri.merchant, function(err, merchantData) { + var balance = $rootScope.availableBalance; + var available = +(balance * config.unitToSatoshi).toFixed(0); + + if (merchantData && available < +merchantData.total) { + err = new Error('No unspent outputs available.'); } - // Regular Address - if (!uri || !uri.merchant) { - var a = new Address(value); - ctrl.$setValidity('validAddress', a.isValid() && a.network().name === config.networkName); - return value; + if (err) { + scope.sendForm.address.$isValid = false; + notification.error('Error', err.message || 'Bad payment server.'); + return; } - notification.info('Fetching Payment', - 'Retrieving Payment Request from ' + uri.merchant); + merchantData.unitTotal = (+merchantData.total / config.unitToSatoshi) + ''; + merchantData.expiration = new Date( + merchantData.pr.pd.expires * 1000).toISOString(); - // Payment Protocol URI (BIP-72) - scope.wallet.fetchPaymentTx(uri.merchant, function(err, merchantData) { - var balance = $rootScope.availableBalance; - var available = +(balance * config.unitToSatoshi).toFixed(0); + $rootScope.merchant = merchantData; - if (merchantData && available < +merchantData.total) { - err = new Error('No unspent outputs available.'); - } + scope.sendForm.address.$isValid = true; - if (err) { - scope.sendForm.address.$isValid = false; - notification.error('Error', err.message || 'Bad payment server.'); - return; - } + scope.sendForm.amount.$setViewValue(merchantData.unitTotal); + scope.sendForm.amount.$render(); + scope.sendForm.amount.$isValid = true; - merchantData.unitTotal = (+merchantData.total / config.unitToSatoshi) + ''; - merchantData.expiration = new Date( - merchantData.pr.pd.expires * 1000).toISOString(); - - $rootScope.merchant = merchantData; - - scope.sendForm.address.$isValid = true; - - scope.sendForm.amount.$setViewValue(merchantData.unitTotal); - scope.sendForm.amount.$render(); - scope.sendForm.amount.$isValid = true; - - // If the address changes to a non-payment-protocol one, - // delete the `merchant` property from the scope. - var unregister = scope.$watch('address', function() { - var val = scope.sendForm.address.$viewValue || ''; - var uri = copay.HDPath.parseBitcoinURI(val); - if (!uri || !uri.merchant) { - delete $rootScope.merchant; - scope.sendForm.amount.$setViewValue(''); - scope.sendForm.amount.$render(); - unregister(); - if ($rootScope.$$phase !== '$apply' - && $rootScope.$$phase !== '$digest') { - $rootScope.$apply(); - } + // If the address changes to a non-payment-protocol one, + // delete the `merchant` property from the scope. + var unregister = scope.$watch('address', function() { + var val = scope.sendForm.address.$viewValue || ''; + var uri = copay.HDPath.parseBitcoinURI(val); + if (!uri || !uri.merchant) { + delete $rootScope.merchant; + scope.sendForm.amount.$setViewValue(''); + scope.sendForm.amount.$render(); + unregister(); + if ($rootScope.$$phase !== '$apply' && $rootScope.$$phase !== '$digest') { + $rootScope.$apply(); } - }); - - if ($rootScope.$$phase !== '$apply' - && $rootScope.$$phase !== '$digest') { - $rootScope.$apply(); } - - notification.info('Payment Request', - 'Server is requesting ' - + merchantData.unitTotal + ' ' - + config.unitName + '.' - + ' Message: ' - + merchantData.pr.pd.memo); }); - ctrl.$setValidity('validAddress', true); + if ($rootScope.$$phase !== '$apply' && $rootScope.$$phase !== '$digest') { + $rootScope.$apply(); + } - return 'Merchant: ' + uri.merchant; - }; + notification.info('Payment Request', + 'Server is requesting ' + merchantData.unitTotal + ' ' + config.unitName + '.' + ' Message: ' + merchantData.pr.pd.memo); + }); - ctrl.$parsers.unshift(validator); - ctrl.$formatters.unshift(validator); - } - }; - } - ]) + ctrl.$setValidity('validAddress', true); + + return 'Merchant: ' + uri.merchant; + }; + + ctrl.$parsers.unshift(validator); + ctrl.$formatters.unshift(validator); + } + }; + }) .directive('enoughAmount', ['$rootScope', function($rootScope) { var bitcore = require('bitcore'); @@ -278,32 +271,34 @@ angular.module('copayApp.directives') restrict: 'A', link: function(scope, element, attrs) { element.bind('click', function() { - window.open('bitcoin:'+attrs.address, '_blank'); + window.open('bitcoin:' + attrs.address, '_blank'); }); } } }) - // From https://gist.github.com/asafge/7430497 - .directive('ngReallyClick', [function() { - return { - restrict: 'A', - link: function(scope, element, attrs) { - element.bind('click', function() { - var message = attrs.ngReallyMessage; - if (message && confirm(message)) { - scope.$apply(attrs.ngReallyClick); - } - }); - } +// From https://gist.github.com/asafge/7430497 +.directive('ngReallyClick', [ + + function() { + return { + restrict: 'A', + link: function(scope, element, attrs) { + element.bind('click', function() { + var message = attrs.ngReallyMessage; + if (message && confirm(message)) { + scope.$apply(attrs.ngReallyClick); + } + }); } } - ]) - .directive('match', function () { + } +]) + .directive('match', function() { return { require: 'ngModel', restrict: 'A', scope: { - match: '=' + match: '=' }, link: function(scope, elem, attrs, ctrl) { scope.$watch(function() { @@ -324,16 +319,18 @@ angular.module('copayApp.directives') return { restric: 'A', - scope: { clipCopy: '=clipCopy' }, + scope: { + clipCopy: '=clipCopy' + }, link: function(scope, elm) { var client = new ZeroClipboard(elm); - client.on( 'ready', function(event) { - client.on( 'copy', function(event) { + client.on('ready', function(event) { + client.on('copy', function(event) { event.clipboardData.setData('text/plain', scope.clipCopy); }); - client.on( 'aftercopy', function(event) { + client.on('aftercopy', function(event) { elm.removeClass('btn-copy').addClass('btn-copied').html('Copied!'); setTimeout(function() { elm.addClass('btn-copy').removeClass('btn-copied').html(''); @@ -341,8 +338,8 @@ angular.module('copayApp.directives') }); }); - client.on( 'error', function(event) { - console.log( 'ZeroClipboard error of type "' + event.name + '": ' + event.message ); + client.on('error', function(event) { + console.log('ZeroClipboard error of type "' + event.name + '": ' + event.message); ZeroClipboard.destroy(); }); } diff --git a/test/unit/controllers/controllersSpec.js b/test/unit/controllers/controllersSpec.js index 3e08db32a..ed257b8ef 100644 --- a/test/unit/controllers/controllersSpec.js +++ b/test/unit/controllers/controllersSpec.js @@ -221,47 +221,6 @@ describe("Unit: Controllers", function() { sinon.assert.callCount(scope.loadTxs, 1); }); - it('#start the example server', function(done) { - startServer(function(err, s) { - if (err) return done(err); - server = s; - server.uri = 'https://localhost:8080/-'; - done(); - }); - }); - - it('#create a payment protocol transaction proposal', function() { - var uri = 'bitcoin:1JqniWpWNA6Yvdivg3y9izLidETnurxRQm?amount=0.00001000&r=https://localhost:8080/-/request'; - sendForm.address.$setViewValue(uri); - sendForm.amount.$setViewValue(1000); - - scope.wallet.totalCopayers = scope.wallet.requiredCopayers = 3; - var spy = sinon.spy(scope.wallet, 'createTx'); - var spy2 = sinon.spy(scope.wallet, 'sendTx'); - scope.submitForm(sendForm); - sinon.assert.callCount(spy, 1); - sinon.assert.callCount(spy2, 0); - }); - - it('#create and send a payment protocol transaction proposal', function() { - var uri = 'bitcoin:1JqniWpWNA6Yvdivg3y9izLidETnurxRQm?amount=0.00001000&r=https://localhost:8080/-/request'; - sendForm.address.$setViewValue(uri); - sendForm.amount.$setViewValue(1000); - - scope.wallet.totalCopayers = scope.wallet.requiredCopayers = 1; - var spy = sinon.spy(scope.wallet, 'createTx'); - var spy2 = sinon.spy(scope.wallet, 'sendTx'); - - scope.submitForm(sendForm); - sinon.assert.callCount(spy, 1); - sinon.assert.callCount(spy2, 1); - }); - - it('#stop the example server', function(done) { - server.close(function() { - done(); - }); - }); }); describe("Unit: Version Controller", function() { From a710ccd3263bf4cd96b015a79e283d54da7c76d8 Mon Sep 17 00:00:00 2001 From: Manuel Araoz Date: Wed, 13 Aug 2014 18:18:29 -0400 Subject: [PATCH 158/178] fix dependencies --- package.json | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 0d804e198..9600d1de6 100644 --- a/package.json +++ b/package.json @@ -11,10 +11,11 @@ }, "version": "0.4.2", "dependencies": { - "preconditions": "^1.0.7", - "sinon": "1.9.1", + "mocha": "^1.18.2", "mocha-lcov-reporter": "0.0.1", - "mocha": "^1.18.2" + "optimist": "^0.6.1", + "preconditions": "^1.0.7", + "sinon": "1.9.1" }, "scripts": { "shell": "node shell/scripts/launch.js", From 967457764cb5aedf988822716b48e80250a52e71 Mon Sep 17 00:00:00 2001 From: Manuel Araoz Date: Wed, 13 Aug 2014 18:42:04 -0400 Subject: [PATCH 159/178] refactor controller code to controller from directive --- js/controllers/send.js | 77 +++++++++++++++++++++++++++++++++++++----- js/directives.js | 56 +----------------------------- karma.conf.js | 2 +- views/send.html | 2 +- 4 files changed, 71 insertions(+), 66 deletions(-) diff --git a/js/controllers/send.js b/js/controllers/send.js index 96eb79c11..95032a9e0 100644 --- a/js/controllers/send.js +++ b/js/controllers/send.js @@ -80,8 +80,7 @@ angular.module('copayApp.controllers').controller('SendController', var message = 'The transaction proposal has been created'; if (merchantData) { if (merchantData.pr.ca) { - message += ' This payment protocol transaction' - + ' has been verified through ' + merchantData.pr.ca + '.'; + message += ' This payment protocol transaction' + ' has been verified through ' + merchantData.pr.ca + '.'; } message += ' Message from server: ' + merchantData.ack.memo; message += ' For merchant: ' + merchantData.pr.pd.payment_url; @@ -94,8 +93,7 @@ angular.module('copayApp.controllers').controller('SendController', var message = 'Transaction id: ' + txid; if (merchantData) { if (merchantData.pr.ca) { - message += ' This payment protocol transaction' - + ' has been verified through ' + merchantData.pr.ca + '.'; + message += ' This payment protocol transaction' + ' has been verified through ' + merchantData.pr.ca + '.'; } message += ' Message from server: ' + merchantData.ack.memo; message += ' For merchant: ' + merchantData.pr.pd.payment_url; @@ -115,9 +113,13 @@ angular.module('copayApp.controllers').controller('SendController', if (address.indexOf('bitcoin:') === 0) { uri = copay.HDPath.parseBitcoinURI(address); } else if (address.indexOf('Merchant: ') === 0) { - uri = { merchant: address.split(/\s+/)[1] }; + uri = { + merchant: address.split(/\s+/)[1] + }; } else if (/^https?:\/\//.test(address)) { - uri = { merchant: address }; + uri = { + merchant: address + }; } if (uri && uri.merchant) { @@ -352,12 +354,11 @@ angular.module('copayApp.controllers').controller('SendController', notification.error('Error', 'There was an error sending the transaction'); } else { if (!merchantData) { - notification.success('Transaction broadcast', 'Transaction id: '+txid); + notification.success('Transaction broadcast', 'Transaction id: ' + txid); } else { var message = 'Transaction ID: ' + txid; if (merchantData.pr.ca) { - message += ' This payment protocol transaction' - + ' has been verified through ' + merchantData.pr.ca + '.'; + message += ' This payment protocol transaction' + ' has been verified through ' + merchantData.pr.ca + '.'; } message += ' Message from server: ' + merchantData.ack.memo; message += ' For merchant: ' + merchantData.pr.pd.payment_url; @@ -399,4 +400,62 @@ angular.module('copayApp.controllers').controller('SendController', $scope.loadTxs(); }; + + $scope.onChanged = function() { + var scope = $scope; + notification.info('Fetching Payment', + 'Retrieving Payment Request from ' + uri.merchant); + + // Payment Protocol URI (BIP-72) + scope.wallet.fetchPaymentTx(uri.merchant, function(err, merchantData) { + var balance = $rootScope.availableBalance; + var available = +(balance * config.unitToSatoshi).toFixed(0); + + if (merchantData && available < +merchantData.total) { + err = new Error('No unspent outputs available.'); + } + + if (err) { + scope.sendForm.address.$isValid = false; + notification.error('Error', err.message || 'Bad payment server.'); + return; + } + + merchantData.unitTotal = (+merchantData.total / config.unitToSatoshi) + ''; + merchantData.expiration = new Date( + merchantData.pr.pd.expires * 1000).toISOString(); + + $rootScope.merchant = merchantData; + + scope.sendForm.address.$isValid = true; + + scope.sendForm.amount.$setViewValue(merchantData.unitTotal); + scope.sendForm.amount.$render(); + scope.sendForm.amount.$isValid = true; + + // If the address changes to a non-payment-protocol one, + // delete the `merchant` property from the scope. + var unregister = scope.$watch('address', function() { + var val = scope.sendForm.address.$viewValue || ''; + var uri = copay.HDPath.parseBitcoinURI(val); + if (!uri || !uri.merchant) { + delete $rootScope.merchant; + scope.sendForm.amount.$setViewValue(''); + scope.sendForm.amount.$render(); + unregister(); + if ($rootScope.$$phase !== '$apply' && $rootScope.$$phase !== '$digest') { + $rootScope.$apply(); + } + } + }); + + if ($rootScope.$$phase !== '$apply' && $rootScope.$$phase !== '$digest') { + $rootScope.$apply(); + } + + notification.info('Payment Request', + 'Server is requesting ' + merchantData.unitTotal + ' ' + config.unitName + '.' + ' Message: ' + merchantData.pr.pd.memo); + }); + }; + }); diff --git a/js/directives.js b/js/directives.js index 145366b65..edb96e1d8 100644 --- a/js/directives.js +++ b/js/directives.js @@ -1,7 +1,7 @@ 'use strict'; angular.module('copayApp.directives') - .directive('validAddress', function($rootScope, notification) { + .directive('validAddress', function() { var bitcore = require('bitcore'); var Address = bitcore.Address; var bignum = bitcore.Bignum; @@ -27,62 +27,8 @@ angular.module('copayApp.directives') return value; } - notification.info('Fetching Payment', - 'Retrieving Payment Request from ' + uri.merchant); - - // Payment Protocol URI (BIP-72) - scope.wallet.fetchPaymentTx(uri.merchant, function(err, merchantData) { - var balance = $rootScope.availableBalance; - var available = +(balance * config.unitToSatoshi).toFixed(0); - - if (merchantData && available < +merchantData.total) { - err = new Error('No unspent outputs available.'); - } - - if (err) { - scope.sendForm.address.$isValid = false; - notification.error('Error', err.message || 'Bad payment server.'); - return; - } - - merchantData.unitTotal = (+merchantData.total / config.unitToSatoshi) + ''; - merchantData.expiration = new Date( - merchantData.pr.pd.expires * 1000).toISOString(); - - $rootScope.merchant = merchantData; - - scope.sendForm.address.$isValid = true; - - scope.sendForm.amount.$setViewValue(merchantData.unitTotal); - scope.sendForm.amount.$render(); - scope.sendForm.amount.$isValid = true; - - // If the address changes to a non-payment-protocol one, - // delete the `merchant` property from the scope. - var unregister = scope.$watch('address', function() { - var val = scope.sendForm.address.$viewValue || ''; - var uri = copay.HDPath.parseBitcoinURI(val); - if (!uri || !uri.merchant) { - delete $rootScope.merchant; - scope.sendForm.amount.$setViewValue(''); - scope.sendForm.amount.$render(); - unregister(); - if ($rootScope.$$phase !== '$apply' && $rootScope.$$phase !== '$digest') { - $rootScope.$apply(); - } - } - }); - - if ($rootScope.$$phase !== '$apply' && $rootScope.$$phase !== '$digest') { - $rootScope.$apply(); - } - - notification.info('Payment Request', - 'Server is requesting ' + merchantData.unitTotal + ' ' + config.unitName + '.' + ' Message: ' + merchantData.pr.pd.memo); - }); ctrl.$setValidity('validAddress', true); - return 'Merchant: ' + uri.merchant; }; diff --git a/karma.conf.js b/karma.conf.js index 7211a9f15..5b9d32890 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -43,9 +43,9 @@ module.exports = function(config) { //App-specific Code 'js/app.js', 'js/routes.js', + 'js/services/*.js', 'js/directives.js', 'js/filters.js', - 'js/services/*.js', 'js/controllers/*.js', 'js/init.js', diff --git a/views/send.html b/views/send.html index 037d4eb45..1edc68d64 100644 --- a/views/send.html +++ b/views/send.html @@ -21,7 +21,7 @@
+ placeholder="Send to" ng-model="address" ngChange="onChanged()" valid-address required>
From 098ed70765d88b051b5ab24877526282f2f68e02 Mon Sep 17 00:00:00 2001 From: Manuel Araoz Date: Wed, 13 Aug 2014 18:49:22 -0400 Subject: [PATCH 160/178] added angular controllers to grunt watch --- Gruntfile.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gruntfile.js b/Gruntfile.js index 809a2cc95..53942da0b 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -24,7 +24,7 @@ module.exports = function(grunt) { tasks: ['markdown'] }, scripts: { - files: ['*.js', '**/*.js', '*.html', '!**/node_modules/**', '!lib/**js', '!browser/vendor-bundle.js', '!js/copayBundle.js'], + files: ['*.js', '*/*/*.js', '**/*.js', '*.html', '!**/node_modules/**', '!lib/**js', '!browser/vendor-bundle.js', '!js/copayBundle.js'], tasks: ['shell'], }, }, From 324dfbc36a8eeb844a9a31215ec09b9b3f0798e8 Mon Sep 17 00:00:00 2001 From: Manuel Araoz Date: Wed, 13 Aug 2014 18:54:07 -0400 Subject: [PATCH 161/178] finished refactor --- js/controllers/send.js | 14 +++++++++++++- js/directives.js | 2 +- views/send.html | 2 +- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/js/controllers/send.js b/js/controllers/send.js index 95032a9e0..c72530dd9 100644 --- a/js/controllers/send.js +++ b/js/controllers/send.js @@ -168,7 +168,6 @@ angular.module('copayApp.controllers').controller('SendController', qrcode.imagedata = context.getImageData(0, 0, qrcode.width, qrcode.height); try { - //alert(JSON.stringify(qrcode.process(context))); qrcode.decode(); } catch (e) { // error decoding QR @@ -403,6 +402,19 @@ angular.module('copayApp.controllers').controller('SendController', $scope.onChanged = function() { var scope = $scope; + var value = scope.address; + var uri; + + if (/^https?:\/\//.test(value)) { + uri = { + merchant: value + }; + } else { + uri = copay.HDPath.parseBitcoinURI(value); + } + if (!uri || !uri.merchant) { + return; + } notification.info('Fetching Payment', 'Retrieving Payment Request from ' + uri.merchant); diff --git a/js/directives.js b/js/directives.js index edb96e1d8..d704b6073 100644 --- a/js/directives.js +++ b/js/directives.js @@ -29,7 +29,7 @@ angular.module('copayApp.directives') ctrl.$setValidity('validAddress', true); - return 'Merchant: ' + uri.merchant; + return uri.merchant; }; ctrl.$parsers.unshift(validator); diff --git a/views/send.html b/views/send.html index 1edc68d64..9861deead 100644 --- a/views/send.html +++ b/views/send.html @@ -21,7 +21,7 @@
+ placeholder="Send to" ng-model="address" ng-change="onChanged()" valid-address required>
From 685c6e54dca07359444cacf2af505051fe3d6a6e Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Wed, 13 Aug 2014 20:21:27 -0400 Subject: [PATCH 162/178] paypro: differentiate safeUnspent and unspent. --- js/models/core/Wallet.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index 71153f565..16303944d 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -113,7 +113,7 @@ Wallet.prototype.unlock = function() { Wallet.prototype.checkAndLock = function() { if (this.getLock()) { return true; - } + } this.setLock(); return false; @@ -927,16 +927,16 @@ Wallet.prototype.receivePaymentRequest = function(options, pr, cb) { raw: pr.serialize().toString('hex') }; - return this.getUnspent(function(err, unspent) { + return this.getUnspent(function(err, safeUnspent, unspent) { if (options.fetch) { if (!unspent || !unspent.length) { return cb(new Error('No unspent outputs available.')); } - self.createPaymentTxSync(options, merchantData, unspent); + self.createPaymentTxSync(options, merchantData, safeUnspent); return cb(null, merchantData, pr); } - var ntxid = self.createPaymentTxSync(options, merchantData, unspent); + var ntxid = self.createPaymentTxSync(options, merchantData, safeUnspent); if (ntxid) { self.sendIndexes(); self.sendTxProposal(ntxid); From f64b1eee28a2462df4509464558e6fb8d235bf36 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Wed, 13 Aug 2014 20:22:14 -0400 Subject: [PATCH 163/178] paypro: allow unconfirmed unspent outputs in paypro tx proposals. --- js/models/core/Wallet.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index 16303944d..7b3a9d916 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -932,11 +932,11 @@ Wallet.prototype.receivePaymentRequest = function(options, pr, cb) { if (!unspent || !unspent.length) { return cb(new Error('No unspent outputs available.')); } - self.createPaymentTxSync(options, merchantData, safeUnspent); + self.createPaymentTxSync(options, merchantData, unspent); return cb(null, merchantData, pr); } - var ntxid = self.createPaymentTxSync(options, merchantData, safeUnspent); + var ntxid = self.createPaymentTxSync(options, merchantData, unspent); if (ntxid) { self.sendIndexes(); self.sendTxProposal(ntxid); From eea59bc0e172615ff18ae8fe912749fc65cf9a50 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Wed, 13 Aug 2014 20:29:02 -0400 Subject: [PATCH 164/178] paypro: better notification if the user has insufficient funds for a PP tx. --- js/controllers/send.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/js/controllers/send.js b/js/controllers/send.js index c72530dd9..577ab2579 100644 --- a/js/controllers/send.js +++ b/js/controllers/send.js @@ -425,11 +425,16 @@ angular.module('copayApp.controllers').controller('SendController', if (merchantData && available < +merchantData.total) { err = new Error('No unspent outputs available.'); + scope.notEnoughAmount = true; + scope.sendForm.amount.$isValid = false; } if (err) { scope.sendForm.address.$isValid = false; notification.error('Error', err.message || 'Bad payment server.'); + if ($rootScope.$$phase !== '$apply' && $rootScope.$$phase !== '$digest') { + $rootScope.$apply(); + } return; } From 8e86826b68f2274cbabac813ccb86d322c1e0bba Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Wed, 13 Aug 2014 20:51:33 -0400 Subject: [PATCH 165/178] paypro: even better notification when user has insufficient funds. --- js/controllers/send.js | 9 +++++++-- js/models/core/Wallet.js | 12 +++++++++++- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/js/controllers/send.js b/js/controllers/send.js index 577ab2579..1bbbf0270 100644 --- a/js/controllers/send.js +++ b/js/controllers/send.js @@ -425,12 +425,17 @@ angular.module('copayApp.controllers').controller('SendController', if (merchantData && available < +merchantData.total) { err = new Error('No unspent outputs available.'); - scope.notEnoughAmount = true; - scope.sendForm.amount.$isValid = false; + err.amount = merchantData.total; } if (err) { scope.sendForm.address.$isValid = false; + if (err.amount) { + scope.sendForm.amount.$setViewValue(+err.amount / config.unitToSatoshi); + scope.sendForm.amount.$render(); + scope.sendForm.amount.$isValid = false; + scope.notEnoughAmount = true; + } notification.error('Error', err.message || 'Bad payment server.'); if ($rootScope.$$phase !== '$apply' && $rootScope.$$phase !== '$digest') { $rootScope.$apply(); diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index 7b3a9d916..e1cf67666 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -932,7 +932,17 @@ Wallet.prototype.receivePaymentRequest = function(options, pr, cb) { if (!unspent || !unspent.length) { return cb(new Error('No unspent outputs available.')); } - self.createPaymentTxSync(options, merchantData, unspent); + try { + self.createPaymentTxSync(options, merchantData, unspent); + } catch (e) { + var msg = e.message || ''; + if (msg.indexOf('not enough unspent tx outputs to fulfill')) { + var sat = /(\d+)/.exec(msg)[1]; + e = new Error('No unspent outputs available.'); + e.amount = sat; + return cb(e); + } + } return cb(null, merchantData, pr); } From ea7948f4209b365980f2dbf0f246f53efe6acceb Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Wed, 13 Aug 2014 21:06:57 -0400 Subject: [PATCH 166/178] paypro: better error display on no unspent outputs. --- js/controllers/send.js | 16 ++++++++++++++++ views/send.html | 2 +- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/js/controllers/send.js b/js/controllers/send.js index 1bbbf0270..1417002c0 100644 --- a/js/controllers/send.js +++ b/js/controllers/send.js @@ -430,13 +430,29 @@ angular.module('copayApp.controllers').controller('SendController', if (err) { scope.sendForm.address.$isValid = false; + if (err.amount) { scope.sendForm.amount.$setViewValue(+err.amount / config.unitToSatoshi); scope.sendForm.amount.$render(); scope.sendForm.amount.$isValid = false; scope.notEnoughAmount = true; + $rootScope.merchantError = true; + var lastAddr = scope.sendForm.address.$viewValue; + var unregister = scope.$watch('address', function() { + if (scope.sendForm.address.$viewValue !== lastAddr) { + delete $rootScope.merchantError; + scope.sendForm.amount.$setViewValue(''); + scope.sendForm.amount.$render(); + unregister(); + if ($rootScope.$$phase !== '$apply' && $rootScope.$$phase !== '$digest') { + $rootScope.$apply(); + } + } + }); } + notification.error('Error', err.message || 'Bad payment server.'); + if ($rootScope.$$phase !== '$apply' && $rootScope.$$phase !== '$digest') { $rootScope.$apply(); } diff --git a/views/send.html b/views/send.html index 9861deead..05328c8e3 100644 --- a/views/send.html +++ b/views/send.html @@ -65,7 +65,7 @@
Date: Wed, 13 Aug 2014 21:10:55 -0400 Subject: [PATCH 167/178] paypro: fix tests for unspent outputs. --- test/test.PayPro.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test.PayPro.js b/test/test.PayPro.js index 288e68762..9e50889a6 100644 --- a/test/test.PayPro.js +++ b/test/test.PayPro.js @@ -139,7 +139,7 @@ describe('PayPro (in Wallet) model', function() { unspentTest[0].scriptPubKey = w.publicKeyRing.getScriptPubKeyHex(1, true, w.publicKey); w.getUnspent = function(cb) { return setTimeout(function() { - return cb(null, unspentTest, []); + return cb(null, unspentTest, unspentTest); }, 1); }; return w; From 8a1a3adf9055c2bee871563c489750a70e17d8a2 Mon Sep 17 00:00:00 2001 From: Manuel Araoz Date: Thu, 14 Aug 2014 10:30:53 -0400 Subject: [PATCH 168/178] fix karma tests after rebase --- util/build.js | 41 +++++++++++++---------------------------- 1 file changed, 13 insertions(+), 28 deletions(-) diff --git a/util/build.js b/util/build.js index d8a41e3cf..71c96b6c8 100644 --- a/util/build.js +++ b/util/build.js @@ -13,7 +13,7 @@ var puts = function(error, stdout, stderr) { //sys.puts(stderr); }; -var pack = function (params) { +var pack = function(params) { var file = require.resolve('soop'); var dir = file.substr(0, file.length - String('soop.js').length); var preludePath = dir + 'example/custom_prelude.js'; @@ -48,9 +48,6 @@ var createBundle = function(opts) { b.require('./copay', { expose: 'copay' }); - b.require('./copay', { - expose: '../copay' - }); b.require('./version'); // b.external('bitcore'); b.require('./js/models/core/WalletFactory', { @@ -60,27 +57,24 @@ var createBundle = function(opts) { b.require('./js/models/core/Wallet', { expose: '../js/models/core/Wallet' }); + b.require('./js/models/core/Wallet', { + expose: '../js/models/core/Wallet' + }); b.require('./js/models/core/Wallet', { expose: '../../js/models/core/Wallet' }); b.require('./test/mocks/FakeStorage', { expose: './mocks/FakeStorage' }); - b.require('./test/mocks/FakePayProServer', { - expose: './mocks/FakePayProServer' - }); - b.require('./test/mocks/FakePayProServer', { - expose: '../../mocks/FakePayProServer' - }); b.require('./test/mocks/FakeBlockchain', { expose: './mocks/FakeBlockchain' }); - b.require('./test/mocks/FakeLocalStorage', { - expose: './mocks/FakeLocalStorage' - }); b.require('./test/mocks/FakeNetwork', { expose: './mocks/FakeNetwork' }); + b.require('./test/mocks/FakePayProServer', { + expose: '../../mocks/FakePayProServer' + }); b.require('./js/models/network/WebRTC', { expose: '../js/models/network/WebRTC' }); @@ -96,24 +90,15 @@ var createBundle = function(opts) { b.require('./js/models/core/Passphrase', { expose: '../js/models/core/Passphrase' }); - b.require('./js/models/core/Message', { - expose: '../js/models/core/Message' - }); - b.require('./config', { - expose: '../config' - }); - b.require('./js/models/core/HDPath', { - expose: '../js/models/core/HDPath' - }); - if (opts.debug) { + if (opts.dontminify) { //include dev dependencies b.require('sinon'); b.require('blanket'); b.require('soop'); } - if (!opts.debug) { + if (!opts.dontminify) { b.transform({ global: true }, 'uglifyify'); @@ -128,10 +113,10 @@ if (require.main === module) { }; var program = require('commander'); program - .version('0.0.1') - .option('-d, --debug', 'Development. Don\'t minify the codem and include debug packages.') - .option('-o, --stdout', 'Specify output as stdout') - .parse(process.argv); + .version('0.0.1') + .option('-d, --dontminify', 'Development. Don\'t minify the code.') + .option('-o, --stdout', 'Specify output as stdout') + .parse(process.argv); createVersion(); var copayBundle = createBundle(program); From fe0bdb3eb3ac262cd814b7a98357b2e2a9fd144d Mon Sep 17 00:00:00 2001 From: Manuel Araoz Date: Thu, 14 Aug 2014 10:42:25 -0400 Subject: [PATCH 169/178] fixing build for index.html tests --- util/build.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/util/build.js b/util/build.js index 71c96b6c8..76d48fb04 100644 --- a/util/build.js +++ b/util/build.js @@ -57,15 +57,15 @@ var createBundle = function(opts) { b.require('./js/models/core/Wallet', { expose: '../js/models/core/Wallet' }); - b.require('./js/models/core/Wallet', { - expose: '../js/models/core/Wallet' - }); b.require('./js/models/core/Wallet', { expose: '../../js/models/core/Wallet' }); b.require('./test/mocks/FakeStorage', { expose: './mocks/FakeStorage' }); + b.require('./js/models/core/Message', { + expose: '../js/models/core/Message' + }); b.require('./test/mocks/FakeBlockchain', { expose: './mocks/FakeBlockchain' }); @@ -90,6 +90,9 @@ var createBundle = function(opts) { b.require('./js/models/core/Passphrase', { expose: '../js/models/core/Passphrase' }); + b.require('./config', { + expose: '../config' + }); if (opts.dontminify) { //include dev dependencies From 055b9da2f558ef76aeb13b728797d1bffb39c2ab Mon Sep 17 00:00:00 2001 From: Manuel Araoz Date: Thu, 14 Aug 2014 10:44:14 -0400 Subject: [PATCH 170/178] fixing build.js one error at a time --- util/build.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/util/build.js b/util/build.js index 76d48fb04..c2de4694d 100644 --- a/util/build.js +++ b/util/build.js @@ -63,6 +63,9 @@ var createBundle = function(opts) { b.require('./test/mocks/FakeStorage', { expose: './mocks/FakeStorage' }); + b.require('./test/mocks/FakeLocalStorage', { + expose: './mocks/FakeLocalStorage' + }); b.require('./js/models/core/Message', { expose: '../js/models/core/Message' }); From 4e07b8812daec356de51946477176b78f2aba928 Mon Sep 17 00:00:00 2001 From: Manuel Araoz Date: Thu, 14 Aug 2014 10:45:52 -0400 Subject: [PATCH 171/178] fixing build.js one error at a time 2 --- util/build.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/util/build.js b/util/build.js index c2de4694d..60e6b81f3 100644 --- a/util/build.js +++ b/util/build.js @@ -93,9 +93,15 @@ var createBundle = function(opts) { b.require('./js/models/core/Passphrase', { expose: '../js/models/core/Passphrase' }); + b.require('./js/models/core/HDPath', { + expose: '../js/models/core/HDPath' + }); b.require('./config', { expose: '../config' }); + b.require('./copay', { + expose: '../copay' + }); if (opts.dontminify) { //include dev dependencies From c56bfc626346f01b319e79410a4c37d899eb8dcb Mon Sep 17 00:00:00 2001 From: Manuel Araoz Date: Thu, 14 Aug 2014 11:00:20 -0400 Subject: [PATCH 172/178] fixing build.js one error at a time 3 --- js/app.js | 2 +- util/build.js | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/js/app.js b/js/app.js index 058dfbba6..268649363 100644 --- a/js/app.js +++ b/js/app.js @@ -20,7 +20,7 @@ var log = function() { } // From the bundle -var copay = require('copay'); +var copay = require('../copay'); var copayApp = window.copayApp = angular.module('copayApp', [ 'ngRoute', diff --git a/util/build.js b/util/build.js index 60e6b81f3..01bb2ebd1 100644 --- a/util/build.js +++ b/util/build.js @@ -78,6 +78,9 @@ var createBundle = function(opts) { b.require('./test/mocks/FakePayProServer', { expose: '../../mocks/FakePayProServer' }); + b.require('./test/mocks/FakeBuilder', { + expose: './mocks/FakeBuilder' + }); b.require('./js/models/network/WebRTC', { expose: '../js/models/network/WebRTC' }); @@ -99,6 +102,10 @@ var createBundle = function(opts) { b.require('./config', { expose: '../config' }); + b.require('./copay'); + b.require('./copay', { + expose: 'copay' + }); b.require('./copay', { expose: '../copay' }); From a731ebd0138f26b70c1bee27af2ec90eb645f7ec Mon Sep 17 00:00:00 2001 From: Manuel Araoz Date: Thu, 14 Aug 2014 11:14:16 -0400 Subject: [PATCH 173/178] fixing build.js one error at a time 4 --- js/app.js | 2 +- util/build.js | 14 +++++--------- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/js/app.js b/js/app.js index 268649363..058dfbba6 100644 --- a/js/app.js +++ b/js/app.js @@ -20,7 +20,7 @@ var log = function() { } // From the bundle -var copay = require('../copay'); +var copay = require('copay'); var copayApp = window.copayApp = angular.module('copayApp', [ 'ngRoute', diff --git a/util/build.js b/util/build.js index 01bb2ebd1..807ce9c92 100644 --- a/util/build.js +++ b/util/build.js @@ -55,10 +55,10 @@ var createBundle = function(opts) { }); b.require('./js/models/core/Wallet'); b.require('./js/models/core/Wallet', { - expose: '../js/models/core/Wallet' + expose: '../../js/models/core/Wallet' }); b.require('./js/models/core/Wallet', { - expose: '../../js/models/core/Wallet' + expose: '../js/models/core/Wallet' }); b.require('./test/mocks/FakeStorage', { expose: './mocks/FakeStorage' @@ -78,6 +78,9 @@ var createBundle = function(opts) { b.require('./test/mocks/FakePayProServer', { expose: '../../mocks/FakePayProServer' }); + b.require('./test/mocks/FakePayProServer', { + expose: './mocks/FakePayProServer' + }); b.require('./test/mocks/FakeBuilder', { expose: './mocks/FakeBuilder' }); @@ -102,13 +105,6 @@ var createBundle = function(opts) { b.require('./config', { expose: '../config' }); - b.require('./copay'); - b.require('./copay', { - expose: 'copay' - }); - b.require('./copay', { - expose: '../copay' - }); if (opts.dontminify) { //include dev dependencies From 2725b7f6e183c92271abf1588a4564f5d2acf1e3 Mon Sep 17 00:00:00 2001 From: Manuel Araoz Date: Thu, 14 Aug 2014 11:19:50 -0400 Subject: [PATCH 174/178] fixing build.js one error at a time 5 --- test/test.storage.LocalEncrypted.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/test/test.storage.LocalEncrypted.js b/test/test.storage.LocalEncrypted.js index 8ad081f06..45aad70bd 100644 --- a/test/test.storage.LocalEncrypted.js +++ b/test/test.storage.LocalEncrypted.js @@ -19,7 +19,11 @@ CryptoJS.AES.decrypt = function(a) { 'use strict'; var chai = chai || require('chai'); var should = chai.should(); -var copay = copay || require('../copay'); +try { + var copay = require('copay'); //browser +} catch (e) { + var copay = require('../copay'); //node +} var LocalEncrypted = copay.StorageLocalEncrypted; var fakeWallet = 'fake-wallet-id'; From 7f07e7b8ac44d906a8ef83610332d45f82704a7b Mon Sep 17 00:00:00 2001 From: Manuel Araoz Date: Thu, 14 Aug 2014 11:25:15 -0400 Subject: [PATCH 175/178] fixing build.js one error at a time 6 --- test/test.TxProposals.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/test/test.TxProposals.js b/test/test.TxProposals.js index 94f51964a..a7a25e727 100644 --- a/test/test.TxProposals.js +++ b/test/test.TxProposals.js @@ -13,7 +13,11 @@ var TransactionBuilder = bitcore.TransactionBuilder; var util = bitcore.util; var networks = bitcore.networks; var sinon = require('sinon'); -var copay = require('../copay'); +try { + var copay = require('copay'); //browser +} catch (e) { + var copay = require('../copay'); //node +} var FakeBuilder = require('./mocks/FakeBuilder'); var TxProposal = copay.TxProposal; From 6c79d3575dadaaa19b32474bd23081b30b798998 Mon Sep 17 00:00:00 2001 From: Manuel Araoz Date: Thu, 14 Aug 2014 11:32:47 -0400 Subject: [PATCH 176/178] fixing build.js one error at a time 7 --- util/build.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/util/build.js b/util/build.js index 807ce9c92..cf90b861e 100644 --- a/util/build.js +++ b/util/build.js @@ -55,10 +55,10 @@ var createBundle = function(opts) { }); b.require('./js/models/core/Wallet'); b.require('./js/models/core/Wallet', { - expose: '../../js/models/core/Wallet' + expose: '../js/models/core/Wallet' }); b.require('./js/models/core/Wallet', { - expose: '../js/models/core/Wallet' + expose: '../../js/models/core/Wallet' }); b.require('./test/mocks/FakeStorage', { expose: './mocks/FakeStorage' @@ -76,10 +76,10 @@ var createBundle = function(opts) { expose: './mocks/FakeNetwork' }); b.require('./test/mocks/FakePayProServer', { - expose: '../../mocks/FakePayProServer' + expose: './mocks/FakePayProServer' }); b.require('./test/mocks/FakePayProServer', { - expose: './mocks/FakePayProServer' + expose: '../../mocks/FakePayProServer' }); b.require('./test/mocks/FakeBuilder', { expose: './mocks/FakeBuilder' From e857e34b1d3b2771d9adbb95c84f6b494345d89b Mon Sep 17 00:00:00 2001 From: Manuel Araoz Date: Thu, 14 Aug 2014 11:51:56 -0400 Subject: [PATCH 177/178] fixing index.html and karma tests --- copay.js | 5 +++++ test/mocks/FakePayProServer.js | 7 ++++++- test/mocks/FakeWallet.js | 8 +++++++- test/test.PayPro.js | 4 ++-- test/test.Wallet.js | 2 +- 5 files changed, 21 insertions(+), 5 deletions(-) diff --git a/copay.js b/copay.js index 5b2711dc9..e4cda4988 100644 --- a/copay.js +++ b/copay.js @@ -14,5 +14,10 @@ var Insight = module.exports.Insight = require('./js/models/blockchain/Insight') var StorageLocalEncrypted = module.exports.StorageLocalEncrypted = require('./js/models/storage/LocalEncrypted'); module.exports.WalletFactory = require('./js/models/core/WalletFactory'); +module.exports.Wallet = require('./js/models/core/Wallet'); module.exports.version = require('./version'); module.exports.API = require('./API'); + + +// test hack :s, will fix +module.exports.FakePayProServer = require('./test/mocks/FakePayProServer'); diff --git a/test/mocks/FakePayProServer.js b/test/mocks/FakePayProServer.js index e1e507201..3fbc05365 100644 --- a/test/mocks/FakePayProServer.js +++ b/test/mocks/FakePayProServer.js @@ -5,7 +5,12 @@ var is_browser = typeof process == 'undefined' var bitcore = bitcore || require('bitcore'); var Buffer = bitcore.Buffer; var PayPro = bitcore.PayPro; -var Wallet = require('../../js/models/core/Wallet'); +try { + var copay = require('copay'); //browser +} catch (e) { + var copay = require('../../copay'); //node +} +var Wallet = copay.Wallet; var x509 = { priv: '' diff --git a/test/mocks/FakeWallet.js b/test/mocks/FakeWallet.js index d5eceef10..d83829e5a 100644 --- a/test/mocks/FakeWallet.js +++ b/test/mocks/FakeWallet.js @@ -1,4 +1,10 @@ -var Wallet = require('../../js/models/core/Wallet'); + +try { + var copay = require('copay'); //browser +} catch (e) { + var copay = require('../copay'); //node +} +var Wallet = copay.Wallet; var FakeWallet = function() { diff --git a/test/test.PayPro.js b/test/test.PayPro.js index 9e50889a6..af6107e35 100644 --- a/test/test.PayPro.js +++ b/test/test.PayPro.js @@ -11,7 +11,7 @@ if (is_browser) { var copay = require('../copay'); //node } var copayConfig = require('../config'); -var Wallet = require('../js/models/core/Wallet'); +var Wallet = copay.Wallet; var PrivateKey = copay.PrivateKey; var Storage = require('./mocks/FakeStorage'); var Network = require('./mocks/FakeNetwork'); @@ -22,7 +22,7 @@ var Transaction = bitcore.Transaction; var Address = bitcore.Address; var PayPro = bitcore.PayPro; var bignum = bitcore.Bignum; -var startServer = require('./mocks/FakePayProServer'); +var startServer = copay.FakePayProServer; // TODO should be require('./mocks/FakePayProServer'); var server; diff --git a/test/test.Wallet.js b/test/test.Wallet.js index f39287428..75a2d9988 100644 --- a/test/test.Wallet.js +++ b/test/test.Wallet.js @@ -10,7 +10,7 @@ if (is_browser) { var copay = require('../copay'); //node } var copayConfig = require('../config'); -var Wallet = require('../js/models/core/Wallet'); +var Wallet = copay.Wallet; var PrivateKey = copay.PrivateKey; var Storage = require('./mocks/FakeStorage'); var Network = require('./mocks/FakeNetwork'); From 1c264deb2c053a71423914232b3149d0025974df Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Thu, 14 Aug 2014 12:29:11 -0400 Subject: [PATCH 178/178] test: use is_browser instead of try/catch to detect browser. --- test/mocks/FakePayProServer.js | 4 ++-- test/mocks/FakeWallet.js | 6 ++++-- test/test.TxProposal.js | 6 ++++-- test/test.storage.LocalEncrypted.js | 6 ++++-- 4 files changed, 14 insertions(+), 8 deletions(-) diff --git a/test/mocks/FakePayProServer.js b/test/mocks/FakePayProServer.js index 3fbc05365..16f6dbafb 100644 --- a/test/mocks/FakePayProServer.js +++ b/test/mocks/FakePayProServer.js @@ -5,9 +5,9 @@ var is_browser = typeof process == 'undefined' var bitcore = bitcore || require('bitcore'); var Buffer = bitcore.Buffer; var PayPro = bitcore.PayPro; -try { +if (is_browser) { var copay = require('copay'); //browser -} catch (e) { +} else { var copay = require('../../copay'); //node } var Wallet = copay.Wallet; diff --git a/test/mocks/FakeWallet.js b/test/mocks/FakeWallet.js index d83829e5a..58d58a458 100644 --- a/test/mocks/FakeWallet.js +++ b/test/mocks/FakeWallet.js @@ -1,7 +1,9 @@ -try { +var is_browser = typeof process == 'undefined' + || typeof process.versions === 'undefined'; +if (is_browser) { var copay = require('copay'); //browser -} catch (e) { +} else { var copay = require('../copay'); //node } var Wallet = copay.Wallet; diff --git a/test/test.TxProposal.js b/test/test.TxProposal.js index 3ac2f310d..1ac3a1981 100644 --- a/test/test.TxProposal.js +++ b/test/test.TxProposal.js @@ -13,9 +13,11 @@ var TransactionBuilder = bitcore.TransactionBuilder; var util = bitcore.util; var networks = bitcore.networks; var sinon = require('sinon'); -try { +var is_browser = typeof process == 'undefined' + || typeof process.versions === 'undefined'; +if (is_browser) { var copay = require('copay'); //browser -} catch (e) { +} else { var copay = require('../copay'); //node } diff --git a/test/test.storage.LocalEncrypted.js b/test/test.storage.LocalEncrypted.js index 45aad70bd..a25f6cc92 100644 --- a/test/test.storage.LocalEncrypted.js +++ b/test/test.storage.LocalEncrypted.js @@ -19,9 +19,11 @@ CryptoJS.AES.decrypt = function(a) { 'use strict'; var chai = chai || require('chai'); var should = chai.should(); -try { +var is_browser = typeof process == 'undefined' + || typeof process.versions === 'undefined'; +if (is_browser) { var copay = require('copay'); //browser -} catch (e) { +} else { var copay = require('../copay'); //node } var LocalEncrypted = copay.StorageLocalEncrypted;