diff --git a/copay.js b/copay.js index fe6b4dbbd..8e8b03089 100644 --- a/copay.js +++ b/copay.js @@ -3,8 +3,8 @@ module.exports.PublicKeyRing = require('./js/models/core/PublicKeyRing'); module.exports.TxProposals = require('./js/models/core/TxProposals'); module.exports.PrivateKey = require('./js/models/core/PrivateKey'); module.exports.Passphrase = require('./js/models/core/Passphrase'); -module.exports.Structure = require('./js/models/core/Structure'); -module.exports.AddressIndex = require('./js/models/core/AddressIndex'); +module.exports.HDPath = require('./js/models/core/HDPath'); +module.exports.HDParams = require('./js/models/core/HDParams'); // components diff --git a/css/main.css b/css/main.css index 266610d4b..4d141325b 100644 --- a/css/main.css +++ b/css/main.css @@ -909,6 +909,10 @@ button, .button, p { margin-left: 0; } +.pointer { + cursor: pointer; +} + .video-box { width: 70px; text-align: center; @@ -1013,7 +1017,7 @@ a.text-white:hover {color: #ccc;} .box-setup .panel { background-color: #2C3E50; - padding: 0.5rem; + padding: 1rem; border: 0; } @@ -1046,4 +1050,27 @@ a.text-white:hover {color: #ccc;} color: #8597A7; } +.panel qrcode { + float: left; + width: 160px; + height: 160px; + padding: 5px 0 0 5px; + background-color: white; +} + +.panel qrcode canvas { + width: 150px; + height: 150px; +} + +.panel .secret { + line-height: 1.3rem; + padding-top: 4rem; + float: left; + margin-left: 2rem; + overflow-wrap: break-word; + width: 55%; + text-align: left; +} + /*-----------------------------------------------------------------*/ diff --git a/css/mobile.css b/css/mobile.css index 37ee18786..e57ce9e7d 100644 --- a/css/mobile.css +++ b/css/mobile.css @@ -102,5 +102,18 @@ border-right: 1px solid #425568; } + .panel .secret { + padding-top: 0.5rem; + display: block; + width: 100%; + margin-left: 0; + text-align: center; + } + + .panel qrcode { + display: block; + float: none; + margin: 0 auto; + } } diff --git a/index.html b/index.html index a531e0d0c..acc29b64d 100644 --- a/index.html +++ b/index.html @@ -3,7 +3,7 @@ - + Copay - Multisignature Wallet @@ -106,6 +106,24 @@ + + + diff --git a/js/controllers/addresses.js b/js/controllers/addresses.js index 72d985653..907bfc7ef 100644 --- a/js/controllers/addresses.js +++ b/js/controllers/addresses.js @@ -19,6 +19,14 @@ angular.module('copayApp.controllers').controller('AddressesController', $scope.openAddressModal = function(address) { var ModalInstanceCtrl = function ($scope, $modalInstance, address) { $scope.address = address; + + $scope.openExternal = function(address) { + var url = 'bitcoin:' + address; + if (window.cordova) return window.open(url, '_blank'); + + window.location = url; + } + $scope.cancel = function () { $modalInstance.dismiss('cancel'); }; diff --git a/js/controllers/join.js b/js/controllers/join.js index 13019b032..ea6d446a2 100644 --- a/js/controllers/join.js +++ b/js/controllers/join.js @@ -1,9 +1,104 @@ 'use strict'; angular.module('copayApp.controllers').controller('JoinController', - function($scope, $rootScope, walletFactory, controllerUtils, Passphrase, notification) { + function($scope, $rootScope, $timeout, walletFactory, controllerUtils, Passphrase, notification) { $scope.loading = false; - + + // QR code Scanner + var cameraInput; + var video; + var canvas; + var $video; + var context; + var localMediaStream; + + var _scan = function(evt) { + if (localMediaStream) { + context.drawImage(video, 0, 0, 300, 225); + + try { + qrcode.decode(); + } catch (e) { + //qrcodeError(e); + } + } + + $timeout(_scan, 500); + }; + + var _successCallback = function(stream) { + video.src = (window.URL && window.URL.createObjectURL(stream)) || stream; + localMediaStream = stream; + video.play(); + $timeout(_scan, 1000); + }; + + var _scanStop = function() { + $scope.showScanner = false; + if (!$scope.isMobile) { + if (localMediaStream && localMediaStream.stop) localMediaStream.stop(); + localMediaStream = null; + video.src = ''; + } + }; + + var _videoError = function(err) { + _scanStop(); + }; + + qrcode.callback = function(data) { + _scanStop(); + + $scope.$apply(function() { + $scope.connectionId = data; + }); + }; + + $scope.cancelScanner = function() { + _scanStop(); + }; + + $scope.openScanner = function() { + if (window.cordova) return $scope.scannerIntent(); + + $scope.showScanner = true; + + // Wait a moment until the canvas shows + $timeout(function() { + canvas = document.getElementById('qr-canvas'); + context = canvas.getContext('2d'); + + if ($scope.isMobile) { + cameraInput = document.getElementById('qrcode-camera'); + cameraInput.addEventListener('change', _scan, false); + } else { + video = document.getElementById('qrcode-scanner-video'); + $video = angular.element(video); + canvas.width = 300; + canvas.height = 225; + context.clearRect(0, 0, 300, 225); + + navigator.getUserMedia({ + video: true + }, _successCallback, _videoError); + } + }, 500); + }; + + $scope.scannerIntent = function() { + cordova.plugins.barcodeScanner.scan( + function onSuccess(result) { + if (result.cancelled) return; + + $scope.connectionId = result.text; + $rootScope.$digest(); + }, + function onError(error) { + alert('Scanning error'); + }); + } + + $scope.join = function(form) { if (form && form.$invalid) { notification.error('Error', 'Please enter the required fields'); diff --git a/js/controllers/send.js b/js/controllers/send.js index af3b819f4..cac79c111 100644 --- a/js/controllers/send.js +++ b/js/controllers/send.js @@ -30,10 +30,6 @@ angular.module('copayApp.controllers').controller('SendController', $scope.amount = amount; } - // Detect protocol - $scope.isHttp = ($window.location.protocol.indexOf('http') === 0); - $scope.isCordova = typeof(window.cordova) != 'undefined'; - navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia; window.URL = window.URL || window.webkitURL || window.mozURL || window.msURL; $scope.isMobile = isMobile.any(); @@ -171,6 +167,8 @@ angular.module('copayApp.controllers').controller('SendController', }; $scope.openScanner = function() { + if (window.cordova) return $scope.scannerIntent(); + $scope.showScanner = true; // Wait a moment until the canvas shows diff --git a/js/controllers/settings.js b/js/controllers/settings.js index c8f1532f8..90128de34 100644 --- a/js/controllers/settings.js +++ b/js/controllers/settings.js @@ -74,8 +74,6 @@ angular.module('copayApp.controllers').controller('SettingsController', unitToSatoshi: $scope.selectedUnit.value, })); - var target = ($window.location.origin !== 'null' ? $window.location.origin : ''); - - $window.location.href = target; + window.location.reload(); }; }); diff --git a/js/controllers/uriPayment.js b/js/controllers/uriPayment.js index 18c02d000..c6bd02f8d 100644 --- a/js/controllers/uriPayment.js +++ b/js/controllers/uriPayment.js @@ -2,7 +2,7 @@ angular.module('copayApp.controllers').controller('UriPaymentController', function($rootScope, $scope, $routeParams, $timeout, $location) { var data = decodeURIComponent($routeParams.data); - $rootScope.pendingPayment = copay.Structure.parseBitcoinURI($routeParams.data); + $rootScope.pendingPayment = copay.HDPath.parseBitcoinURI($routeParams.data); $scope.protocol = $rootScope.pendingPayment.protocol; $scope.address = $rootScope.pendingPayment.address; diff --git a/js/models/core/AddressIndex.js b/js/models/core/AddressIndex.js deleted file mode 100644 index d6f953a53..000000000 --- a/js/models/core/AddressIndex.js +++ /dev/null @@ -1,99 +0,0 @@ -'use strict'; - -var imports = require('soop').imports(); -var preconditions = require('preconditions').singleton(); -var Structure = require('./Structure'); - -function AddressIndex(opts) { - opts = opts || {}; - this.cosigner = opts.cosigner - this.changeIndex = opts.changeIndex || 0; - this.receiveIndex = opts.receiveIndex || 0; - - if (typeof this.cosigner === 'undefined') { - this.cosigner = Structure.SHARED_INDEX; - } -} - -AddressIndex.init = function(totalCopayers) { - preconditions.shouldBeNumber(totalCopayers); - var indexes = [new AddressIndex()]; - for (var i = 0 ; i < totalCopayers ; i++) { - indexes.push(new AddressIndex({cosigner: i})); - } - return indexes; -} - -AddressIndex.fromList = function(indexes) { - return indexes.map(function(i) { return AddressIndex.fromObj(i); }); -} - -AddressIndex.fromObj = function(data) { - if (data instanceof AddressIndex) { - throw new Error('bad data format: Did you use .toObj()?'); - } - return new AddressIndex(data); -}; - -AddressIndex.serialize = function(indexes) { - return indexes.map(function(i) { return i.toObj(); }); -} - -AddressIndex.update = function(shared, totalCopayers) { - var indexes = this.init(totalCopayers); - indexes[0].changeIndex = shared.changeIndex; - indexes[0].receiveIndex = shared.receiveIndex; - return this.serialize(indexes); -}; - -AddressIndex.prototype.toObj = function() { - return { - cosigner: this.cosigner, - changeIndex: this.changeIndex, - receiveIndex: this.receiveIndex - }; -}; - -AddressIndex.prototype.checkRange = function(index, isChange) { - if ((isChange && index > this.changeIndex) || - (!isChange && index > this.receiveIndex)) { - throw new Error('Out of bounds at index ' + index + ' isChange: ' + isChange); - } -}; - -AddressIndex.prototype.getChangeIndex = function() { - return this.changeIndex; -}; - -AddressIndex.prototype.getReceiveIndex = function() { - return this.receiveIndex; -}; - -AddressIndex.prototype.increment = function(isChange) { - if (isChange) { - this.changeIndex++; - } else { - this.receiveIndex++; - } -}; - -AddressIndex.prototype.merge = function(inAddressIndex) { - preconditions.shouldBeObject(inAddressIndex) - .checkArgument(this.cosigner == inAddressIndex.cosigner); - - var hasChanged = false; - - // Indexes - if (inAddressIndex.changeIndex > this.changeIndex) { - this.changeIndex = inAddressIndex.changeIndex; - hasChanged = true; - } - - if (inAddressIndex.receiveIndex > this.receiveIndex) { - this.receiveIndex = inAddressIndex.receiveIndex; - hasChanged = true; - } - return hasChanged; -}; - -module.exports = require('soop')(AddressIndex); diff --git a/js/models/core/HDParams.js b/js/models/core/HDParams.js new file mode 100644 index 000000000..9c84108b9 --- /dev/null +++ b/js/models/core/HDParams.js @@ -0,0 +1,99 @@ +'use strict'; + +var preconditions = require('preconditions').singleton(); +var HDPath = require('./HDPath'); + +function HDParams(opts) { + opts = opts || {}; + + //opts.cosigner is for backwards compatibility only + this.copayerIndex = typeof opts.copayerIndex === 'undefined' ? opts.cosigner : opts.copayerIndex; + this.changeIndex = opts.changeIndex || 0; + this.receiveIndex = opts.receiveIndex || 0; + + if (typeof this.copayerIndex === 'undefined') { + this.copayerIndex = HDPath.SHARED_INDEX; + } +} + +HDParams.init = function(totalCopayers) { + preconditions.shouldBeNumber(totalCopayers); + var ret = [new HDParams()]; + for (var i = 0 ; i < totalCopayers ; i++) { + ret.push(new HDParams({copayerIndex: i})); + } + return ret; +} + +HDParams.fromList = function(hdParams) { + return hdParams.map(function(i) { return HDParams.fromObj(i); }); +} + +HDParams.fromObj = function(data) { + if (data instanceof HDParams) { + throw new Error('bad data format: Did you use .toObj()?'); + } + return new HDParams(data); +}; + +HDParams.serialize = function(hdParams) { + return hdParams.map(function(i) { return i.toObj(); }); +} + +HDParams.update = function(shared, totalCopayers) { + var hdParams = this.init(totalCopayers); + hdParams[0].changeIndex = shared.changeIndex; + hdParams[0].receiveIndex = shared.receiveIndex; + return this.serialize(hdParams); +}; + +HDParams.prototype.toObj = function() { + return { + copayerIndex: this.copayerIndex, + changeIndex: this.changeIndex, + receiveIndex: this.receiveIndex + }; +}; + +HDParams.prototype.checkRange = function(index, isChange) { + if ((isChange && index > this.changeIndex) || + (!isChange && index > this.receiveIndex)) { + throw new Error('Out of bounds at index ' + index + ' isChange: ' + isChange); + } +}; + +HDParams.prototype.getChangeIndex = function() { + return this.changeIndex; +}; + +HDParams.prototype.getReceiveIndex = function() { + return this.receiveIndex; +}; + +HDParams.prototype.increment = function(isChange) { + if (isChange) { + this.changeIndex++; + } else { + this.receiveIndex++; + } +}; + +HDParams.prototype.merge = function(inHDParams) { + preconditions.shouldBeObject(inHDParams) + .checkArgument(this.copayerIndex == inHDParams.copayerIndex); + + var hasChanged = false; + + if (inHDParams.changeIndex > this.changeIndex) { + this.changeIndex = inHDParams.changeIndex; + hasChanged = true; + } + + if (inHDParams.receiveIndex > this.receiveIndex) { + this.receiveIndex = inHDParams.receiveIndex; + hasChanged = true; + } + return hasChanged; +}; + +module.exports = HDParams; diff --git a/js/models/core/HDPath.js b/js/models/core/HDPath.js new file mode 100644 index 000000000..2e32ade7e --- /dev/null +++ b/js/models/core/HDPath.js @@ -0,0 +1,75 @@ +'use strict'; + +var preconditions = require('preconditions').singleton(); + +function HDPath() {} + +/* + * Based on https://github.com/maraoz/bips/blob/master/bip-NNNN.mediawiki + * m / purpose' / copayerIndex / change / addressIndex + */ +var PURPOSE = 45; +var MAX_NON_HARDENED = 0x80000000 - 1; + +var SHARED_INDEX = MAX_NON_HARDENED - 0; +var ID_INDEX = MAX_NON_HARDENED - 1; + +var BIP45_PUBLIC_PREFIX = 'm/' + PURPOSE + '\''; +HDPath.BIP45_PUBLIC_PREFIX = BIP45_PUBLIC_PREFIX; + +HDPath.Branch = function(addressIndex, isChange, copayerIndex) { + preconditions.shouldBeNumber(addressIndex); + preconditions.shouldBeBoolean(isChange); + var ret = 'm/' + + (typeof copayerIndex !== 'undefined' ? copayerIndex : SHARED_INDEX) + '/' + + (isChange ? 1 : 0) + '/' + + addressIndex; + return ret; +}; + +HDPath.FullBranch = function(addressIndex, isChange, copayerIndex) { + var sub = HDPath.Branch(addressIndex, isChange, copayerIndex); + sub = sub.substring(2); + return BIP45_PUBLIC_PREFIX + '/' + sub; +}; + +HDPath.indicesForPath = function(path) { + preconditions.shouldBeString(path); + var s = path.split('/'); + return { + isChange: s[3] === '1', + index: parseInt(s[4]), + copayerIndex: parseInt(s[2]) + }; +}; + +HDPath.IdFullBranch = HDPath.FullBranch(0, false, ID_INDEX); +HDPath.IdBranch = HDPath.Branch(0, false, ID_INDEX); +HDPath.PURPOSE = PURPOSE; +HDPath.MAX_NON_HARDENED = MAX_NON_HARDENED; +HDPath.SHARED_INDEX = SHARED_INDEX; +HDPath.ID_INDEX = ID_INDEX; + +HDPath.parseBitcoinURI = function(uri) { + var ret = {}; + var data = decodeURIComponent(uri); + var splitDots = data.split(':'); + ret.protocol = splitDots[0]; + data = splitDots[1]; + 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; + } + + return ret; +}; + +module.exports = HDPath; diff --git a/js/models/core/PrivateKey.js b/js/models/core/PrivateKey.js index 9abd30e95..25c57fd60 100644 --- a/js/models/core/PrivateKey.js +++ b/js/models/core/PrivateKey.js @@ -1,13 +1,12 @@ 'use strict'; -var imports = require('soop').imports(); var bitcore = require('bitcore'); var HK = bitcore.HierarchicalKey; var WalletKey = bitcore.WalletKey; var networks = bitcore.networks; var util = bitcore.util; -var Structure = require('./Structure'); +var HDPath = require('./HDPath'); function PrivateKey(opts) { opts = opts || {}; @@ -41,7 +40,7 @@ PrivateKey.prototype.getIdKey = function() { }; PrivateKey.prototype.cacheId = function() { - var path = Structure.IdFullBranch; + var path = HDPath.IdFullBranch; var idhk = this.bip.derive(path); this.idkey = idhk.eckey; this.id = idhk.eckey.public.toString('hex'); @@ -50,7 +49,7 @@ PrivateKey.prototype.cacheId = function() { PrivateKey.prototype.deriveBIP45Branch = function() { if (!this.bip45Branch) { - this.bip45Branch = this.bip.derive(Structure.BIP45_PUBLIC_PREFIX); + this.bip45Branch = this.bip.derive(HDPath.BIP45_PUBLIC_PREFIX); } return this.bip45Branch; } @@ -103,7 +102,7 @@ PrivateKey.prototype.getForPath = function(path) { }; PrivateKey.prototype.get = function(index, isChange, cosigner) { - var path = Structure.FullBranch(index, isChange, cosigner); + var path = HDPath.FullBranch(index, isChange, cosigner); return this.getForPath(path); }; @@ -123,4 +122,4 @@ PrivateKey.prototype.getAll = function(receiveIndex, changeIndex, cosigner) { -module.exports = require('soop')(PrivateKey); +module.exports = PrivateKey; diff --git a/js/models/core/PublicKeyRing.js b/js/models/core/PublicKeyRing.js index 423c083eb..ad7fe6f15 100644 --- a/js/models/core/PublicKeyRing.js +++ b/js/models/core/PublicKeyRing.js @@ -1,13 +1,12 @@ 'use strict'; -var imports = require('soop').imports(); var preconditions = require('preconditions').instance(); var bitcore = require('bitcore'); var HK = bitcore.HierarchicalKey; var PrivateKey = require('./PrivateKey'); -var Structure = require('./Structure'); -var AddressIndex = require('./AddressIndex'); +var HDPath = require('./HDPath'); +var HDParams = require('./HDParams'); var Address = bitcore.Address; var Script = bitcore.Script; @@ -24,14 +23,14 @@ function PublicKeyRing(opts) { this.copayersHK = opts.copayersHK || []; - this.indexes = opts.indexes ? AddressIndex.fromList(opts.indexes) - : AddressIndex.init(this.totalCopayers); + this.indexes = opts.indexes ? HDParams.fromList(opts.indexes) + : HDParams.init(this.totalCopayers); - this.publicKeysCache = opts.publicKeysCache || {}; - this.nicknameFor = opts.nicknameFor || {}; - this.copayerIds = []; - this.copayersBackup = opts.copayersBackup || []; - this.addressToPath = {}; + this.publicKeysCache = opts.publicKeysCache || {}; + this.nicknameFor = opts.nicknameFor || {}; + this.copayerIds = []; + this.copayersBackup = opts.copayersBackup || []; + this.addressToPath = {}; } PublicKeyRing.fromObj = function(data) { @@ -41,7 +40,7 @@ PublicKeyRing.fromObj = function(data) { // Support old indexes schema if (!Array.isArray(data.indexes)) { - data.indexes = AddressIndex.update(data.indexes, data.totalCopayers); + data.indexes = HDParams.update(data.indexes, data.totalCopayers); } var ret = new PublicKeyRing(data); @@ -59,7 +58,7 @@ PublicKeyRing.prototype.toObj = function() { networkName: this.network.name, requiredCopayers: this.requiredCopayers, totalCopayers: this.totalCopayers, - indexes: AddressIndex.serialize(this.indexes), + indexes: HDParams.serialize(this.indexes), copayersBackup: this.copayersBackup, copayersExtPubKeys: this.copayersHK.map(function(b) { @@ -103,14 +102,14 @@ PublicKeyRing.prototype._checkKeys = function() { PublicKeyRing.prototype._newExtendedPublicKey = function() { return new PrivateKey({ - networkName: this.network.name - }) - .deriveBIP45Branch() - .extendedPublicKeyString(); + networkName: this.network.name + }) + .deriveBIP45Branch() + .extendedPublicKeyString(); }; PublicKeyRing.prototype._updateBip = function(index) { - var hk = this.copayersHK[index].derive(Structure.IdBranch); + var hk = this.copayersHK[index].derive(HDPath.IdBranch); this.copayerIds[index] = hk.eckey.public.toString('hex'); }; @@ -149,10 +148,10 @@ PublicKeyRing.prototype.addCopayer = function(newEpk, nickname) { return newEpk; }; -PublicKeyRing.prototype.getPubKeys = function(index, isChange, cosigner) { +PublicKeyRing.prototype.getPubKeys = function(index, isChange, copayerIndex) { this._checkKeys(); - var path = Structure.Branch(index, isChange, cosigner); + var path = HDPath.Branch(index, isChange, copayerIndex); var pubKeys = this.publicKeysCache[path]; if (!pubKeys) { pubKeys = []; @@ -175,26 +174,27 @@ PublicKeyRing.prototype.getPubKeys = function(index, isChange, cosigner) { }; // TODO this could be cached -PublicKeyRing.prototype.getRedeemScript = function(index, isChange, cosigner) { - var pubKeys = this.getPubKeys(index, isChange, cosigner); +PublicKeyRing.prototype.getRedeemScript = function(index, isChange, copayerIndex) { + var pubKeys = this.getPubKeys(index, isChange, copayerIndex); var script = Script.createMultisig(this.requiredCopayers, pubKeys); return script; }; // TODO this could be cached PublicKeyRing.prototype.getAddress = function(index, isChange, id) { - var cosigner = this.getCosigner(id); - var script = this.getRedeemScript(index, isChange, cosigner); + var copayerIndex = this.getCosigner(id); + var script = this.getRedeemScript(index, isChange, copayerIndex); var address = Address.fromScript(script, this.network.name); - this.addressToPath[address.toString()] = Structure.FullBranch(index, isChange, cosigner); + this.addressToPath[address.toString()] = HDPath.FullBranch(index, isChange, copayerIndex); return address; }; // Overloaded to receive a PubkeyString or a consigner index -PublicKeyRing.prototype.getIndex = function(id) { - var cosigner = this.getCosigner(id); - var index = this.indexes.filter(function(i) { return i.cosigner == cosigner }); - if (index.length != 1) throw new Error('no index for cosigner'); +PublicKeyRing.prototype.getHDParams = function(id) { + var copayerIndex = this.getCosigner(id); + var index = this.indexes.filter(function(i) { return i.copayerIndex == copayerIndex }); + if (index.length != 1) throw new Error('no index for copayerIndex'); + return index[0]; }; @@ -206,18 +206,18 @@ PublicKeyRing.prototype.pathForAddress = function(address) { // TODO this could be cached PublicKeyRing.prototype.getScriptPubKeyHex = function(index, isChange, pubkey) { - var cosigner = this.getCosigner(pubkey); - var addr = this.getAddress(index, isChange, cosigner); + var copayerIndex = this.getCosigner(pubkey); + var addr = this.getAddress(index, isChange, copayerIndex); return Script.createP2SH(addr.payload()).getBuffer().toString('hex'); }; //generate a new address, update index. PublicKeyRing.prototype.generateAddress = function(isChange, pubkey) { isChange = !!isChange; - var addrIndex = this.getIndex(pubkey); - var index = isChange ? addrIndex.getChangeIndex() : addrIndex.getReceiveIndex(); - var ret = this.getAddress(index, isChange, addrIndex.cosigner); - addrIndex.increment(isChange); + var HDParams = this.getHDParams(pubkey); + var index = isChange ? HDParams.getChangeIndex() : HDParams.getReceiveIndex(); + var ret = this.getAddress(index, isChange, HDParams.copayerIndex); + HDParams.increment(isChange); return ret; }; @@ -228,7 +228,7 @@ PublicKeyRing.prototype.getAddresses = function(opts) { }; PublicKeyRing.prototype.getCosigner = function(pubKey) { - if (typeof pubKey == 'undefined') return Structure.SHARED_INDEX; + if (typeof pubKey == 'undefined') return HDPath.SHARED_INDEX; if (typeof pubKey == 'number') return pubKey; var sorted = this.copayersHK.map(function(h, i){ @@ -245,51 +245,51 @@ PublicKeyRing.prototype.getCosigner = function(pubKey) { PublicKeyRing.prototype.getAddressesInfo = function(opts, pubkey) { var ret = []; var self = this; - var cosigner = pubkey && this.getCosigner(pubkey); + var copayerIndex = pubkey && this.getCosigner(pubkey); this.indexes.forEach(function(index) { - ret = ret.concat(self.getAddressesInfoForIndex(index, opts, cosigner)); + ret = ret.concat(self.getAddressesInfoForIndex(index, opts, copayerIndex)); }); return ret; } -PublicKeyRing.prototype.getAddressesInfoForIndex = function(index, opts, cosigner) { +PublicKeyRing.prototype.getAddressesInfoForIndex = function(index, opts, copayerIndex) { opts = opts || {}; - var isOwned = index.cosigner == Structure.SHARED_INDEX - || index.cosigner == cosigner; + var isOwned = index.copayerIndex == HDPath.SHARED_INDEX + || index.copayerIndex == copayerIndex; - var ret = []; - if (!opts.excludeChange) { - for (var i = 0; i < index.changeIndex; i++) { - var a = this.getAddress(i, true, index.cosigner); - ret.unshift({ - address: a, - addressStr: a.toString(), - isChange: true, - owned: isOwned - }); + var ret = []; + if (!opts.excludeChange) { + for (var i = 0; i < index.changeIndex; i++) { + var a = this.getAddress(i, true, index.copayerIndex); + ret.unshift({ + address: a, + addressStr: a.toString(), + isChange: true, + owned: isOwned + }); + } } - } - if (!opts.excludeMain) { - for (var i = 0; i < index.receiveIndex; i++) { - var a = this.getAddress(i, false, index.cosigner); - ret.unshift({ - address: a, - addressStr: a.toString(), - isChange: false, - owned: isOwned - }); + if (!opts.excludeMain) { + for (var i = 0; i < index.receiveIndex; i++) { + var a = this.getAddress(i, false, index.copayerIndex); + ret.unshift({ + address: a, + addressStr: a.toString(), + isChange: false, + owned: isOwned + }); + } } - } - return ret; + return ret; }; // TODO this could be cached PublicKeyRing.prototype._addScriptMap = function(map, path) { - var p = Structure.indicesForPath(path); - var script = this.getRedeemScript(p.index, p.isChange, p.cosigner); + var p = HDPath.indicesForPath(path); + var script = this.getRedeemScript(p.index, p.isChange, p.copayerIndex); map[Address.fromScript(script, this.network.name).toString()] = script.getBuffer().toString('hex'); }; @@ -310,18 +310,18 @@ PublicKeyRing.prototype._checkInPKR = function(inPKR, ignoreId) { if (this.network.name !== inPKR.network.name) { throw new Error('Network mismatch. Should be ' + this.network.name + - ' and found ' + inPKR.network.name); + ' and found ' + inPKR.network.name); } if ( this.requiredCopayers && inPKR.requiredCopayers && - (this.requiredCopayers !== inPKR.requiredCopayers)) - throw new Error('inPKR requiredCopayers mismatch ' + this.requiredCopayers + '!=' + inPKR.requiredCopayers); + (this.requiredCopayers !== inPKR.requiredCopayers)) + throw new Error('inPKR requiredCopayers mismatch ' + this.requiredCopayers + '!=' + inPKR.requiredCopayers); - if ( - this.totalCopayers && inPKR.totalCopayers && - (this.totalCopayers !== inPKR.totalCopayers)) - throw new Error('inPKR totalCopayers mismatch' + this.totalCopayers + '!=' + inPKR.requiredCopayers); + if ( + this.totalCopayers && inPKR.totalCopayers && + (this.totalCopayers !== inPKR.totalCopayers)) + throw new Error('inPKR totalCopayers mismatch' + this.totalCopayers + '!=' + inPKR.requiredCopayers); }; @@ -393,7 +393,7 @@ PublicKeyRing.prototype.mergeIndexes = function(indexes) { var hasChanged = false; indexes.forEach(function(theirs) { - var mine = self.getIndex(theirs.cosigner); + var mine = self.getHDParams(theirs.copayerIndex); hasChanged |= mine.merge(theirs); }); @@ -414,4 +414,4 @@ PublicKeyRing.prototype.mergeBackups = function(backups) { } -module.exports = require('soop')(PublicKeyRing); +module.exports = PublicKeyRing; diff --git a/js/models/core/Structure.js b/js/models/core/Structure.js deleted file mode 100644 index 54b51e4da..000000000 --- a/js/models/core/Structure.js +++ /dev/null @@ -1,76 +0,0 @@ -'use strict'; - -var imports = require('soop').imports(); -var preconditions = require('preconditions').singleton(); - -function Structure() {} - -/* - * Based on https://github.com/maraoz/bips/blob/master/bip-NNNN.mediawiki - * m / purpose' / cosigner_index / change / address_index - */ -var PURPOSE = 45; -var MAX_NON_HARDENED = 0x80000000 - 1; - -var SHARED_INDEX = MAX_NON_HARDENED - 0; -var ID_INDEX = MAX_NON_HARDENED - 1; - -var BIP45_PUBLIC_PREFIX = 'm/' + PURPOSE + '\''; -Structure.BIP45_PUBLIC_PREFIX = BIP45_PUBLIC_PREFIX; - -Structure.Branch = function(address_index, isChange, cosigner_index) { - preconditions.shouldBeNumber(address_index); - preconditions.shouldBeBoolean(isChange); - var ret = 'm/' + - (typeof cosigner_index !== 'undefined' ? cosigner_index : SHARED_INDEX) + '/' + - (isChange ? 1 : 0) + '/' + - address_index; - return ret; -}; - -Structure.FullBranch = function(address_index, isChange, cosigner_index) { - var sub = Structure.Branch(address_index, isChange, cosigner_index); - sub = sub.substring(2); - return BIP45_PUBLIC_PREFIX + '/' + sub; -}; - -Structure.indicesForPath = function(path) { - preconditions.shouldBeString(path); - var s = path.split('/'); - return { - isChange: s[3] === '1', - index: parseInt(s[4]), - cosigner: parseInt(s[2]) - }; -}; - -Structure.IdFullBranch = Structure.FullBranch(0, false, ID_INDEX); -Structure.IdBranch = Structure.Branch(0, false, ID_INDEX); -Structure.PURPOSE = PURPOSE; -Structure.MAX_NON_HARDENED = MAX_NON_HARDENED; -Structure.SHARED_INDEX = SHARED_INDEX; -Structure.ID_INDEX = ID_INDEX; - -Structure.parseBitcoinURI = function(uri) { - var ret = {}; - var data = decodeURIComponent(uri); - var splitDots = data.split(':'); - ret.protocol = splitDots[0]; - data = splitDots[1]; - 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; - } - - return ret; -}; - -module.exports = require('soop')(Structure); diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index cf5e13839..db37355cf 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -15,7 +15,7 @@ var SecureRandom = bitcore.SecureRandom; var Base58Check = bitcore.Base58.base58Check; var Address = bitcore.Address; -var AddressIndex = require('./AddressIndex'); +var HDParams = require('./HDParams'); var PublicKeyRing = require('./PublicKeyRing'); var TxProposals = require('./TxProposals'); var PrivateKey = require('./PrivateKey'); @@ -93,7 +93,7 @@ Wallet.prototype.connectToAll = function() { Wallet.prototype._handleIndexes = function(senderId, data, isInbound) { this.log('RECV INDEXES:', data); - var inIndexes = AddressIndex.fromList(data.indexes); + var inIndexes = HDParams.fromList(data.indexes); var hasChanged = this.publicKeyRing.mergeIndexes(inIndexes); if (hasChanged) { this.emit('publicKeyRingUpdated'); @@ -132,8 +132,10 @@ Wallet.prototype._handlePublicKeyRing = function(senderId, data, isInbound) { Wallet.prototype._handleTxProposal = function(senderId, data) { this.log('RECV TXPROPOSAL: ', data); - var inTxp = TxProposals.TxProposal.fromObj(data.txProposal, Wallet.builderOpts); + + + var valid = inTxp.isValid(); if (!valid) { var corruptEvent = { @@ -458,7 +460,7 @@ Wallet.prototype.sendPublicKeyRing = function(recipients) { }); }; Wallet.prototype.sendIndexes = function(recipients) { - var indexes = AddressIndex.serialize(this.publicKeyRing.indexes); + var indexes = HDParams.serialize(this.publicKeyRing.indexes); this.log('### INDEXES TO:', recipients || 'All', indexes); this.send(recipients, { diff --git a/js/routes.js b/js/routes.js index 3f7308341..9db46fdc1 100644 --- a/js/routes.js +++ b/js/routes.js @@ -67,7 +67,7 @@ angular .module('copayApp') .config(function($locationProvider) { $locationProvider - .html5Mode(true) + .html5Mode(false) .hashPrefix('!'); }) .run(function($rootScope, $location) { diff --git a/js/services/uriHandler.js b/js/services/uriHandler.js index 59a8304c0..da9dd8695 100644 --- a/js/services/uriHandler.js +++ b/js/services/uriHandler.js @@ -4,8 +4,11 @@ var UriHandler = function() {}; UriHandler.prototype.register = function() { var base = window.location.origin + '/'; - var url = base + '#/uri-payment/%s'; - navigator.registerProtocolHandler('bitcoin', url, 'Copay'); + var url = base + '#!/uri-payment/%s'; + + if(navigator.registerProtocolHandler) { + navigator.registerProtocolHandler('bitcoin', url, 'Copay'); + } }; angular.module('copayApp.services').value('uriHandler', new UriHandler()); diff --git a/mobile/build.sh b/mobile/build.sh index 50ece436a..de4f83362 100644 --- a/mobile/build.sh +++ b/mobile/build.sh @@ -54,7 +54,7 @@ checkOK # Copy all app files echo "${OpenColor}${Green}* Copying all app files...${CloseColor}" cd $BUILDDIR/.. -cp -af {css,font,img,js,lib,sound,config.js,version.js,$BUILDDIR/cordova.js,$BUILDDIR/cordova_plugins.js,$BUILDDIR/plugins} $APPDIR +cp -af {css,font,img,js,lib,sound,views,config.js,version.js,$BUILDDIR/cordova.js,$BUILDDIR/cordova_plugins.js,$BUILDDIR/plugins} $APPDIR checkOK sed "s/<\!-- PLACEHOLDER: CORDOVA SRIPT -->/