From 320de62f137715d238b7cf1c25dcd73efd6e4e09 Mon Sep 17 00:00:00 2001 From: Matias Alejo Garcia Date: Fri, 6 Mar 2015 12:00:10 -0300 Subject: [PATCH] bwc --- .bowerrc | 3 - .ctags | 2 - .gitignore | 143 +- .jshint | 15 - Gruntfile.js | 262 +- Makefile | 18 +- app.js | 53 +- TODO.md => atom-shell.md | 5 + bower.json | 26 +- browser-extensions/chrome/README.md | 9 - browser-extensions/chrome/build.sh | 64 - browser-extensions/chrome/initial.js | 8 - browser-extensions/chrome/manifest.json | 19 - browser-extensions/chrome/tile.png | Bin 30719 -> 0 bytes browser-extensions/exclude | 19 - browser-extensions/include | 16 - config.js | 96 - copay.js | 26 - cordova/android/AndroidManifest.xml | 4 +- cordova/build.sh | 21 +- cordova/wp/Properties/WMAppManifest.xml | 1 + css/foundation-icons.css | 594 --- css/foundation.min.css | 1 - css/src/animation.css | 1279 ------- css/src/desktop.css | 181 - css/src/mobile.css | 532 --- font/foundation-icons.eot | Bin 54568 -> 0 bytes font/foundation-icons.svg | 970 ----- font/foundation-icons.ttf | Bin 56976 -> 0 bytes font/foundation-icons.woff | Bin 32020 -> 0 bytes font/ubuntu-bold-italic.woff | Bin 25780 -> 0 bytes font/ubuntu-bold.woff | Bin 26292 -> 0 bytes font/ubuntu-light.woff | Bin 21248 -> 0 bytes font/ubuntu.woff | Bin 22416 -> 0 bytes img/logo.svg | 18 - index.html | 157 - init.js | 29 - initial.js | 8 - js/app.js | 69 - js/controllers/copayers.js | 72 - js/controllers/create.js | 78 - js/controllers/createProfile.js | 202 - js/controllers/history.js | 204 -- js/controllers/home.js | 216 -- js/controllers/homeWallet.js | 103 - js/controllers/import.js | 110 - js/controllers/index.js | 34 - js/controllers/join.js | 160 - js/controllers/more.js | 183 - js/controllers/paymentUri.js | 20 - js/controllers/receive.js | 106 - js/controllers/send.js | 602 --- js/controllers/settings.js | 86 - js/controllers/sidebar.js | 121 - js/controllers/unsupported.js | 9 - js/controllers/version.js | 34 - js/models/Async.js | 432 --- js/models/Compatibility.js | 256 -- js/models/HDParams.js | 274 -- js/models/HDPath.js | 116 - js/models/Identity.js | 905 ----- js/models/Insight.js | 346 -- js/models/PluginManager.js | 57 - js/models/PrivateKey.js | 264 -- js/models/PublicKeyRing.js | 803 ---- js/models/TxProposal.js | 624 ---- js/models/TxProposals.js | 156 - js/models/Wallet.js | 2846 --------------- js/models/WalletLock.js | 116 - js/plugins/EncryptedInsightStorage.js | 59 - js/plugins/EncryptedLocalStorage.js | 77 - js/plugins/GoogleDrive.js | 301 -- js/plugins/InsightStorage.js | 287 -- js/plugins/LocalStorage.js | 106 - js/services/applicationService.js | 28 - js/services/backupService.js | 86 - js/services/compatibility.js | 13 - js/services/configService.js | 36 - js/services/identityService.js | 363 -- js/services/localstorageService.js | 9 - js/services/pendingTxsService.js | 51 - js/services/pinService.js | 115 - js/services/rate.js | 8 - js/services/request.js | 6 - js/services/txStatus.js | 36 - js/shell.js | 122 - js/util/HTTP.js | 79 - js/util/crypto.js | 83 - js/util/csv.js | 95 - js/util/log.js | 150 - karma.conf.js | 118 - launch.js | 34 - lib/bitcore.js | 636 ---- lib/sjcl.js | 58 - package.json | 86 +- popup.html | 32 - {font => public/font}/icomoon.eot | Bin {font => public/font}/icomoon.svg | 0 {font => public/font}/icomoon.ttf | Bin {font => public/font}/icomoon.woff | Bin {img => public/img}/ajax-loader.gif | Bin {img => public/img}/avatar.jpg | Bin {img => public/img}/change-avatar.png | Bin {img => public/img}/clipo-pin-enter.png | Bin {img => public/img}/clipo-pin.png | Bin {img => public/img}/clipo-signin.png | Bin {img => public/img}/clipo-signup1.png | Bin {img => public/img}/clipo-signup2-1.png | Bin {img => public/img}/clipo-signup2.png | Bin {img => public/img}/clipo-signup3.png | Bin {img => public/img}/favicon.ico | Bin public/img/icon-tour1.png | Bin 0 -> 15091 bytes {img => public/img}/icons/copy.png | Bin {img => public/img}/icons/icon-16.png | Bin {img => public/img}/icons/icon-32.png | Bin {img => public/img}/icons/icon-64.png | Bin {img => public/img}/icons/icon.png | Bin public/img/img-tour1.png | Bin 0 -> 20832 bytes {img => public/img}/logo-negative-beta.svg | 0 {img => public/img}/logo-negative.png | Bin {img => public/img}/logo-negative.svg | 0 {img => public/img}/logo.png | Bin public/img/logo.svg | 20 + {img => public/img}/notification.png | Bin {img => public/img}/satoshi.gif | Bin {img => public/img}/step-1.png | Bin {img => public/img}/step-1.svg | 0 {img => public/img}/step-2.png | Bin {img => public/img}/step-2.svg | 0 {img => public/img}/step-3.png | Bin {img => public/img}/step-3.svg | 0 public/img/tour1.png | Bin 0 -> 104939 bytes public/img/tour2.png | Bin 0 -> 140943 bytes public/img/tour3.png | Bin 0 -> 122338 bytes public/img/tour4.png | Bin 0 -> 114970 bytes public/img/tour5.png | Bin 0 -> 42704 bytes public/index.html | 59 + public/views/add.html | 32 + public/views/backup.html | 62 + public/views/copayers.html | 57 + public/views/create.html | 80 + public/views/createProfile.html | 22 + {views => public/views}/devLogin.html | 0 .../views}/dummy-translations.html | 0 public/views/history.html | 56 + public/views/import.html | 45 + public/views/importLegacy.html | 75 + {views => public/views}/importProfile.html | 17 +- public/views/includes/clientError.html | 11 + public/views/includes/copayers.html | 10 + public/views/includes/loading.html | 6 + public/views/includes/menu.html | 19 + public/views/includes/notifications.html | 13 + public/views/includes/offline.html | 11 + public/views/includes/password.html | 43 + {views => public/views}/includes/photo.html | 0 public/views/includes/pin.html | 54 + public/views/includes/sidebar.html | 28 + public/views/includes/topbar.html | 22 + public/views/includes/transaction.html | 25 + public/views/includes/version.html | 5 + public/views/join.html | 66 + .../views}/modals/address-book.html | 0 public/views/modals/confirmation.html | 16 + public/views/modals/copayers.html | 25 + public/views/modals/paypro.html | 50 + public/views/modals/qr-address.html | 26 + public/views/modals/scanner.html | 16 + public/views/modals/tx-details.html | 100 + public/views/modals/tx-status.html | 32 + public/views/modals/txp-details.html | 143 + .../views}/modals/walletSelection.html | 0 public/views/paymentUri.html | 23 + public/views/preferences.html | 55 + public/views/preferencesAltCurrency.html | 8 + public/views/preferencesBwsUrl.html | 9 + public/views/preferencesColor.html | 7 + public/views/preferencesDeleteWallet.html | 17 + public/views/preferencesUnit.html | 7 + {views => public/views}/profile.html | 0 public/views/receive.html | 71 + {views => public/views}/send.html | 110 +- public/views/settings.html | 60 + views/home.html => public/views/signin.html | 65 +- public/views/splash/1.html | 17 + public/views/splash/2.html | 3 + public/views/splash/3.html | 3 + public/views/splash/4.html | 3 + public/views/splash/5.html | 15 + {views => public/views}/unsupported.html | 2 +- public/views/walletHome.html | 114 + {views => public/views}/warning.html | 0 server.js | 6 - setup/karma.js | 16 - setup/node.js | 16 - shell/README.md | 12 - shell/assets/darwin/Info.plist | 40 - shell/assets/darwin/copay.icns | Bin 32921 -> 0 bytes shell/assets/linux/Copay.desktop | 7 - shell/assets/linux/install.sh | 13 - shell/assets/win32/build-installer.nsi | 148 - shell/assets/win32/logo.ico | Bin 99678 -> 0 bytes shell/bin/README.md | 8 - shell/config.json | 10 - shell/index.js | 63 - shell/lib/app-menu.js | 143 - shell/lib/message-handler.js | 81 - shell/lib/system-tray.js | 16 - shell/scripts/build-linux.js | 70 - shell/scripts/dist.js | 181 - shell/scripts/download-atom-shell.js | 106 - shell/scripts/launch.js | 39 - shell/scripts/lib/download.js | 104 - sound/online.wav | Bin 28440 -> 0 bytes sound/transaction.mp3 | Bin 12539 -> 0 bytes {css/src => src/css}/icons.css | 26 - {css/src => src/css}/main.css | 923 ++--- src/css/mobile.css | 650 ++++ src/js/app.js | 25 + src/js/controllers/backup.js | 66 + src/js/controllers/copayers.js | 102 + src/js/controllers/create.js | 75 + src/js/controllers/createProfile.js | 29 + {js => src/js}/controllers/devLogin.js | 0 .../js}/controllers/emailConfirmation.js | 0 src/js/controllers/history.js | 85 + src/js/controllers/import.js | 81 + src/js/controllers/importLegacy.js | 63 + {js => src/js}/controllers/importProfile.js | 33 +- src/js/controllers/index.js | 517 +++ src/js/controllers/join.js | 158 + src/js/controllers/menu.js | 27 + src/js/controllers/password.js | 41 + src/js/controllers/paymentUri.js | 60 + src/js/controllers/pinController.js | 73 + src/js/controllers/preferences.js | 54 + src/js/controllers/preferencesAltCurrency.js | 57 + src/js/controllers/preferencesBwsUrl.js | 33 + src/js/controllers/preferencesColor.js | 36 + src/js/controllers/preferencesDelete.js | 72 + src/js/controllers/preferencesUnit.js | 59 + {js => src/js}/controllers/profile.js | 15 +- src/js/controllers/receive.js | 87 + src/js/controllers/send.js | 499 +++ src/js/controllers/settings.js | 26 + src/js/controllers/sidebar.js | 52 + {js => src/js}/controllers/signOut.js | 0 src/js/controllers/signin.js | 174 + src/js/controllers/topbar.js | 127 + src/js/controllers/unsupported.js | 7 + src/js/controllers/version.js | 6 + .../js}/controllers/walletForPayment.js | 5 +- src/js/controllers/walletHome.js | 213 ++ {js => src/js}/controllers/warning.js | 0 {js => src/js/directives}/directives.js | 141 +- {js => src/js/filters}/filters.js | 9 +- {js => src/js}/init.js | 25 +- src/js/models/profile.js | 41 + src/js/routes.js | 525 +++ src/js/services/applicationService.js | 24 + src/js/services/backupService.js | 83 + {js => src/js}/services/balanceService.js | 21 +- src/js/services/bitcore.js | 6 + src/js/services/configService.js | 125 + {js => src/js}/services/go.js | 49 +- {js => src/js}/services/isCordova.js | 0 {js => src/js}/services/isMobile.js | 0 src/js/services/legacyImportService.js | 139 + src/js/services/localStorage.js | 92 + {js => src/js}/services/notifications.js | 25 +- src/js/services/notificationsService.js | 94 + {js => src/js}/services/pluginManager.js | 0 src/js/services/profileService.js | 359 ++ .../js/services/rateService.js | 105 +- src/js/services/sjcl.js | 7 + src/js/services/storageService.js | 111 + src/js/services/txStatus.js | 47 + {js => src/js}/services/uriHandler.js | 2 +- test/Compatibility.js | 58 - test/HDParams.js | 143 - test/HDPath.js | 71 - test/Identity.js | 1617 -------- test/PrivateKey.js | 141 - test/PublicKeyRing.js | 607 --- test/RateService.js | 437 --- test/TxProposal.js | 817 ----- test/TxProposals.js | 298 -- test/Wallet.js | 3242 ----------------- test/blockchain.Insight.js | 518 --- test/controllers/menu.test.js | 15 + test/karma.conf.js | 98 + test/lib/chai-expect.js | 1 - test/lib/chai-should.js | 1 - test/mocha.conf.js | 5 - test/mocha.opts | 1 - test/mocks/FakeBlockchain.js | 69 - test/mocks/FakeBlockchainSocket.js | 45 - test/mocks/FakeNetwork.js | 93 - test/network.Async.js | 549 --- .../controllers => old}/controllersSpec.js | 0 .../directives => old}/directivesSpec.js | 0 test/{unit/filters => old}/filtersSpec.js | 0 test/{unit/services => old}/servicesSpec.js | 0 test/plugin.insight.js | 243 -- test/plugin.localstorage.js | 73 - test/run.sh | 5 - test/util.crypto.js | 89 - test/util.csv.js | 165 - test/util.http.js | 89 - test/util.log.js | 60 - util/build.js | 164 - util/build_bitcore.js | 41 - util/build_sjcl.sh | 6 - util/find_m_n.js | 123 - util/version.js | 25 + views/add.html | 22 - views/copayers.html | 92 - views/create.html | 94 - views/createProfile.html | 235 -- views/errors/404.html | 16 - views/history.html | 80 - views/homeWallet.html | 90 - views/import.html | 100 - views/includes/bottombar-mobile.html | 13 - views/includes/copayers.html | 17 - views/includes/head.html | 51 - views/includes/loading.html | 16 - views/includes/notifications.html | 16 - views/includes/pin-number.html | 25 - views/includes/pin.html | 21 - views/includes/scanner.html | 15 - views/includes/sidebar-mobile.html | 67 - views/includes/sidebar.html | 86 - views/includes/transaction.html | 31 - views/includes/version.html | 5 - views/join.html | 80 - views/modals/paypro.html | 37 - views/modals/qr-address.html | 26 - views/modals/tx-details.html | 81 - views/modals/tx-status.html | 21 - views/modals/txp-details.html | 132 - views/more.html | 218 -- views/receive.html | 71 - views/settings.html | 88 - webapp/build.sh | 66 - webapp/index-download-chrome.html | 22 - webapp/index-download-firefox.html | 20 - webapp/index-download.html | 21 - 348 files changed, 7756 insertions(+), 30885 deletions(-) delete mode 100644 .bowerrc delete mode 100644 .ctags delete mode 100644 .jshint rename TODO.md => atom-shell.md (98%) delete mode 100644 browser-extensions/chrome/README.md delete mode 100755 browser-extensions/chrome/build.sh delete mode 100644 browser-extensions/chrome/initial.js delete mode 100644 browser-extensions/chrome/manifest.json delete mode 100644 browser-extensions/chrome/tile.png delete mode 100644 browser-extensions/exclude delete mode 100644 browser-extensions/include delete mode 100644 config.js delete mode 100644 copay.js delete mode 100644 css/foundation-icons.css delete mode 100644 css/foundation.min.css delete mode 100644 css/src/animation.css delete mode 100644 css/src/desktop.css delete mode 100644 css/src/mobile.css delete mode 100644 font/foundation-icons.eot delete mode 100644 font/foundation-icons.svg delete mode 100644 font/foundation-icons.ttf delete mode 100644 font/foundation-icons.woff delete mode 100644 font/ubuntu-bold-italic.woff delete mode 100644 font/ubuntu-bold.woff delete mode 100644 font/ubuntu-light.woff delete mode 100644 font/ubuntu.woff delete mode 100644 img/logo.svg delete mode 100644 index.html delete mode 100644 init.js delete mode 100644 initial.js delete mode 100644 js/app.js delete mode 100644 js/controllers/copayers.js delete mode 100644 js/controllers/create.js delete mode 100644 js/controllers/createProfile.js delete mode 100644 js/controllers/history.js delete mode 100644 js/controllers/home.js delete mode 100644 js/controllers/homeWallet.js delete mode 100644 js/controllers/import.js delete mode 100644 js/controllers/index.js delete mode 100644 js/controllers/join.js delete mode 100644 js/controllers/more.js delete mode 100644 js/controllers/paymentUri.js delete mode 100644 js/controllers/receive.js delete mode 100644 js/controllers/send.js delete mode 100644 js/controllers/settings.js delete mode 100644 js/controllers/sidebar.js delete mode 100644 js/controllers/unsupported.js delete mode 100644 js/controllers/version.js delete mode 100644 js/models/Async.js delete mode 100644 js/models/Compatibility.js delete mode 100644 js/models/HDParams.js delete mode 100644 js/models/HDPath.js delete mode 100644 js/models/Identity.js delete mode 100644 js/models/Insight.js delete mode 100644 js/models/PluginManager.js delete mode 100644 js/models/PrivateKey.js delete mode 100644 js/models/PublicKeyRing.js delete mode 100644 js/models/TxProposal.js delete mode 100644 js/models/TxProposals.js delete mode 100644 js/models/Wallet.js delete mode 100644 js/models/WalletLock.js delete mode 100644 js/plugins/EncryptedInsightStorage.js delete mode 100644 js/plugins/EncryptedLocalStorage.js delete mode 100644 js/plugins/GoogleDrive.js delete mode 100644 js/plugins/InsightStorage.js delete mode 100644 js/plugins/LocalStorage.js delete mode 100644 js/services/applicationService.js delete mode 100644 js/services/backupService.js delete mode 100644 js/services/compatibility.js delete mode 100644 js/services/configService.js delete mode 100644 js/services/identityService.js delete mode 100644 js/services/localstorageService.js delete mode 100644 js/services/pendingTxsService.js delete mode 100644 js/services/pinService.js delete mode 100644 js/services/rate.js delete mode 100644 js/services/request.js delete mode 100644 js/services/txStatus.js delete mode 100644 js/shell.js delete mode 100644 js/util/HTTP.js delete mode 100644 js/util/crypto.js delete mode 100644 js/util/csv.js delete mode 100644 js/util/log.js delete mode 100644 karma.conf.js delete mode 100755 launch.js delete mode 100644 lib/bitcore.js delete mode 100644 lib/sjcl.js delete mode 100644 popup.html rename {font => public/font}/icomoon.eot (100%) rename {font => public/font}/icomoon.svg (100%) rename {font => public/font}/icomoon.ttf (100%) rename {font => public/font}/icomoon.woff (100%) rename {img => public/img}/ajax-loader.gif (100%) rename {img => public/img}/avatar.jpg (100%) rename {img => public/img}/change-avatar.png (100%) rename {img => public/img}/clipo-pin-enter.png (100%) rename {img => public/img}/clipo-pin.png (100%) rename {img => public/img}/clipo-signin.png (100%) rename {img => public/img}/clipo-signup1.png (100%) rename {img => public/img}/clipo-signup2-1.png (100%) rename {img => public/img}/clipo-signup2.png (100%) rename {img => public/img}/clipo-signup3.png (100%) rename {img => public/img}/favicon.ico (100%) create mode 100644 public/img/icon-tour1.png rename {img => public/img}/icons/copy.png (100%) rename {img => public/img}/icons/icon-16.png (100%) rename {img => public/img}/icons/icon-32.png (100%) rename {img => public/img}/icons/icon-64.png (100%) rename {img => public/img}/icons/icon.png (100%) create mode 100644 public/img/img-tour1.png rename {img => public/img}/logo-negative-beta.svg (100%) rename {img => public/img}/logo-negative.png (100%) rename {img => public/img}/logo-negative.svg (100%) rename {img => public/img}/logo.png (100%) create mode 100644 public/img/logo.svg rename {img => public/img}/notification.png (100%) rename {img => public/img}/satoshi.gif (100%) rename {img => public/img}/step-1.png (100%) rename {img => public/img}/step-1.svg (100%) rename {img => public/img}/step-2.png (100%) rename {img => public/img}/step-2.svg (100%) rename {img => public/img}/step-3.png (100%) rename {img => public/img}/step-3.svg (100%) create mode 100644 public/img/tour1.png create mode 100644 public/img/tour2.png create mode 100644 public/img/tour3.png create mode 100644 public/img/tour4.png create mode 100644 public/img/tour5.png create mode 100644 public/index.html create mode 100644 public/views/add.html create mode 100644 public/views/backup.html create mode 100644 public/views/copayers.html create mode 100644 public/views/create.html create mode 100644 public/views/createProfile.html rename {views => public/views}/devLogin.html (100%) rename {views => public/views}/dummy-translations.html (100%) create mode 100644 public/views/history.html create mode 100644 public/views/import.html create mode 100644 public/views/importLegacy.html rename {views => public/views}/importProfile.html (77%) create mode 100644 public/views/includes/clientError.html create mode 100644 public/views/includes/copayers.html create mode 100644 public/views/includes/loading.html create mode 100644 public/views/includes/menu.html create mode 100644 public/views/includes/notifications.html create mode 100644 public/views/includes/offline.html create mode 100644 public/views/includes/password.html rename {views => public/views}/includes/photo.html (100%) create mode 100644 public/views/includes/pin.html create mode 100644 public/views/includes/sidebar.html create mode 100644 public/views/includes/topbar.html create mode 100644 public/views/includes/transaction.html create mode 100644 public/views/includes/version.html create mode 100644 public/views/join.html rename {views => public/views}/modals/address-book.html (100%) create mode 100644 public/views/modals/confirmation.html create mode 100644 public/views/modals/copayers.html create mode 100644 public/views/modals/paypro.html create mode 100644 public/views/modals/qr-address.html create mode 100644 public/views/modals/scanner.html create mode 100644 public/views/modals/tx-details.html create mode 100644 public/views/modals/tx-status.html create mode 100644 public/views/modals/txp-details.html rename {views => public/views}/modals/walletSelection.html (100%) create mode 100644 public/views/paymentUri.html create mode 100644 public/views/preferences.html create mode 100644 public/views/preferencesAltCurrency.html create mode 100644 public/views/preferencesBwsUrl.html create mode 100644 public/views/preferencesColor.html create mode 100644 public/views/preferencesDeleteWallet.html create mode 100644 public/views/preferencesUnit.html rename {views => public/views}/profile.html (100%) create mode 100644 public/views/receive.html rename {views => public/views}/send.html (56%) create mode 100644 public/views/settings.html rename views/home.html => public/views/signin.html (70%) create mode 100644 public/views/splash/1.html create mode 100644 public/views/splash/2.html create mode 100644 public/views/splash/3.html create mode 100644 public/views/splash/4.html create mode 100644 public/views/splash/5.html rename {views => public/views}/unsupported.html (90%) create mode 100644 public/views/walletHome.html rename {views => public/views}/warning.html (100%) delete mode 100644 server.js delete mode 100644 setup/karma.js delete mode 100644 setup/node.js delete mode 100644 shell/README.md delete mode 100644 shell/assets/darwin/Info.plist delete mode 100644 shell/assets/darwin/copay.icns delete mode 100644 shell/assets/linux/Copay.desktop delete mode 100755 shell/assets/linux/install.sh delete mode 100644 shell/assets/win32/build-installer.nsi delete mode 100644 shell/assets/win32/logo.ico delete mode 100644 shell/bin/README.md delete mode 100644 shell/config.json delete mode 100644 shell/index.js delete mode 100644 shell/lib/app-menu.js delete mode 100644 shell/lib/message-handler.js delete mode 100644 shell/lib/system-tray.js delete mode 100644 shell/scripts/build-linux.js delete mode 100644 shell/scripts/dist.js delete mode 100644 shell/scripts/download-atom-shell.js delete mode 100644 shell/scripts/launch.js delete mode 100644 shell/scripts/lib/download.js delete mode 100644 sound/online.wav delete mode 100644 sound/transaction.mp3 rename {css/src => src/css}/icons.css (78%) rename {css/src => src/css}/main.css (60%) create mode 100644 src/css/mobile.css create mode 100644 src/js/app.js create mode 100644 src/js/controllers/backup.js create mode 100644 src/js/controllers/copayers.js create mode 100644 src/js/controllers/create.js create mode 100644 src/js/controllers/createProfile.js rename {js => src/js}/controllers/devLogin.js (100%) rename {js => src/js}/controllers/emailConfirmation.js (100%) create mode 100644 src/js/controllers/history.js create mode 100644 src/js/controllers/import.js create mode 100644 src/js/controllers/importLegacy.js rename {js => src/js}/controllers/importProfile.js (64%) create mode 100644 src/js/controllers/index.js create mode 100644 src/js/controllers/join.js create mode 100644 src/js/controllers/menu.js create mode 100644 src/js/controllers/password.js create mode 100644 src/js/controllers/paymentUri.js create mode 100644 src/js/controllers/pinController.js create mode 100644 src/js/controllers/preferences.js create mode 100644 src/js/controllers/preferencesAltCurrency.js create mode 100644 src/js/controllers/preferencesBwsUrl.js create mode 100644 src/js/controllers/preferencesColor.js create mode 100644 src/js/controllers/preferencesDelete.js create mode 100644 src/js/controllers/preferencesUnit.js rename {js => src/js}/controllers/profile.js (87%) create mode 100644 src/js/controllers/receive.js create mode 100644 src/js/controllers/send.js create mode 100644 src/js/controllers/settings.js create mode 100644 src/js/controllers/sidebar.js rename {js => src/js}/controllers/signOut.js (100%) create mode 100644 src/js/controllers/signin.js create mode 100644 src/js/controllers/topbar.js create mode 100644 src/js/controllers/unsupported.js create mode 100644 src/js/controllers/version.js rename {js => src/js}/controllers/walletForPayment.js (95%) create mode 100644 src/js/controllers/walletHome.js rename {js => src/js}/controllers/warning.js (100%) rename {js => src/js/directives}/directives.js (70%) rename {js => src/js/filters}/filters.js (85%) rename {js => src/js}/init.js (63%) create mode 100644 src/js/models/profile.js create mode 100644 src/js/routes.js create mode 100644 src/js/services/applicationService.js create mode 100644 src/js/services/backupService.js rename {js => src/js}/services/balanceService.js (75%) create mode 100644 src/js/services/bitcore.js create mode 100644 src/js/services/configService.js rename {js => src/js}/services/go.js (67%) rename {js => src/js}/services/isCordova.js (100%) rename {js => src/js}/services/isMobile.js (100%) create mode 100644 src/js/services/legacyImportService.js create mode 100644 src/js/services/localStorage.js rename {js => src/js}/services/notifications.js (90%) create mode 100644 src/js/services/notificationsService.js rename {js => src/js}/services/pluginManager.js (100%) create mode 100644 src/js/services/profileService.js rename js/models/RateService.js => src/js/services/rateService.js (62%) create mode 100644 src/js/services/sjcl.js create mode 100644 src/js/services/storageService.js create mode 100644 src/js/services/txStatus.js rename {js => src/js}/services/uriHandler.js (89%) delete mode 100644 test/Compatibility.js delete mode 100644 test/HDParams.js delete mode 100644 test/HDPath.js delete mode 100644 test/Identity.js delete mode 100644 test/PrivateKey.js delete mode 100644 test/PublicKeyRing.js delete mode 100644 test/RateService.js delete mode 100644 test/TxProposal.js delete mode 100644 test/TxProposals.js delete mode 100644 test/Wallet.js delete mode 100644 test/blockchain.Insight.js create mode 100644 test/controllers/menu.test.js create mode 100644 test/karma.conf.js delete mode 100644 test/lib/chai-expect.js delete mode 100644 test/lib/chai-should.js delete mode 100644 test/mocha.conf.js delete mode 100644 test/mocha.opts delete mode 100644 test/mocks/FakeBlockchain.js delete mode 100644 test/mocks/FakeBlockchainSocket.js delete mode 100644 test/mocks/FakeNetwork.js delete mode 100644 test/network.Async.js rename test/{unit/controllers => old}/controllersSpec.js (100%) rename test/{unit/directives => old}/directivesSpec.js (100%) rename test/{unit/filters => old}/filtersSpec.js (100%) rename test/{unit/services => old}/servicesSpec.js (100%) delete mode 100644 test/plugin.insight.js delete mode 100644 test/plugin.localstorage.js delete mode 100755 test/run.sh delete mode 100644 test/util.crypto.js delete mode 100644 test/util.csv.js delete mode 100644 test/util.http.js delete mode 100644 test/util.log.js delete mode 100644 util/build.js delete mode 100755 util/build_bitcore.js delete mode 100755 util/build_sjcl.sh delete mode 100755 util/find_m_n.js create mode 100755 util/version.js delete mode 100644 views/add.html delete mode 100644 views/copayers.html delete mode 100644 views/create.html delete mode 100644 views/createProfile.html delete mode 100644 views/errors/404.html delete mode 100644 views/history.html delete mode 100644 views/homeWallet.html delete mode 100644 views/import.html delete mode 100644 views/includes/bottombar-mobile.html delete mode 100644 views/includes/copayers.html delete mode 100644 views/includes/head.html delete mode 100644 views/includes/loading.html delete mode 100644 views/includes/notifications.html delete mode 100644 views/includes/pin-number.html delete mode 100644 views/includes/pin.html delete mode 100644 views/includes/scanner.html delete mode 100644 views/includes/sidebar-mobile.html delete mode 100644 views/includes/sidebar.html delete mode 100644 views/includes/transaction.html delete mode 100644 views/includes/version.html delete mode 100644 views/join.html delete mode 100644 views/modals/paypro.html delete mode 100644 views/modals/qr-address.html delete mode 100644 views/modals/tx-details.html delete mode 100644 views/modals/tx-status.html delete mode 100644 views/modals/txp-details.html delete mode 100644 views/more.html delete mode 100644 views/receive.html delete mode 100644 views/settings.html delete mode 100644 webapp/build.sh delete mode 100644 webapp/index-download-chrome.html delete mode 100644 webapp/index-download-firefox.html delete mode 100644 webapp/index-download.html diff --git a/.bowerrc b/.bowerrc deleted file mode 100644 index b5ecdd172..000000000 --- a/.bowerrc +++ /dev/null @@ -1,3 +0,0 @@ -{ - "directory": "lib" -} diff --git a/.ctags b/.ctags deleted file mode 100644 index f42671741..000000000 --- a/.ctags +++ /dev/null @@ -1,2 +0,0 @@ ---exclude=js/copayBundle.js ---exclude=lib/bitcore/browser/bundle.js diff --git a/.gitignore b/.gitignore index f075c785b..550fb9bbf 100644 --- a/.gitignore +++ b/.gitignore @@ -1,85 +1,82 @@ -# from https://github.com/github/gitignore/blob/master/Node.gitignore -lib-cov -*.seed -*.log -*.csv -*.dat -*.out -*.pid -*.gz -*.sw* -*.sig -tags -pids -logs -results -build - -node_modules - -# extras -*.swp -*.swo -*~ -.project -peerdb.json - -npm-debug.log -.nodemonignore - -.DS_Store -public/lib/* -public/js/angularjs-all.js -public/js/main.js -public/js/vendors.js - -public/css/main.css - -README.html - -lib/* -!lib/socket.io.js -!lib/sjcl.js - -js/copayBundle.js -js/copayMain.js -css/copay.min.css -css/vendors.min.css - # translation po/* !po/*.po -js/translations.js +src/js/translations.js -webapp -browser-extensions/chrome/copay-chrome-extension -browser-extensions/chrome/copay-chrome-extension.zip -browser-extensions/firefox/firefox-addon -browser-extensions/firefox/data -browser-extensions/firefox/copay.xpi -version.js -!js/controllers/version.js +# version +src/js/version.js +# cordova cordova/project/* cordova/*.keystore -coverage/ +# Logs +logs +*.log -shell/bin/linux -shell/bin/darwin -shell/bin/win32 +# readme +README.html -shell/scripts/bin -shell/scripts/build +# Runtime data +pids +*.pid +*.seed -dist/darwin -dist/linux -dist/windows -dist/web -dist/*.dmg -dist/*.tar.gz -dist/*.exe +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov -doc/ -/node_modules -/*-cov +# Coverage directory used by tools like istanbul +coverage + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Compiled binary addons (http://nodejs.org/api/addons.html) +build/Release + +# Dependency directory +# Commenting this out is preferred by some people, see +# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git- +node_modules +bower_components + +# Users Environment Variables +.lock-wscript + +# OSX + +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear on external disk +.Spotlight-V100 +.Trashes + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +# VIM ignore + +[._]*.s[a-w][a-z] +[._]s[a-w][a-z] +*.un~ +Session.vim +.netrwhist +*~ + +# copay public +public/icons/* +public/css/* +public/lib/* +public/js/* diff --git a/.jshint b/.jshint deleted file mode 100644 index 219ea40f2..000000000 --- a/.jshint +++ /dev/null @@ -1,15 +0,0 @@ -{ - "camelcase": true, - "curly": true, - "eqeqeq": true, - "freeze": true, - "indent": 2, - "newcap": true, - "quotmark": "single", - "maxdepth": 3, - "maxstatements": 15, - "maxlen": 80, - "eqnull": true, - "funcscope": true, - "node": true -} diff --git a/Gruntfile.js b/Gruntfile.js index 2cb1d039b..575fd0f64 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -1,9 +1,8 @@ module.exports = function(grunt) { - require('load-grunt-tasks')(grunt); - // Project Configuration grunt.initConfig({ + pkg: grunt.file.readJSON('package.json'), release: { options: { bump: true, @@ -26,11 +25,11 @@ module.exports = function(grunt) { } }, exec: { - prod: { - command: 'node ./util/build.js' + version: { + command: 'node ./util/version.js' }, - dev: { - command: 'node ./util/build.js -d' + clear: { + command: 'rm -Rf bower_components node_modules' } }, watch: { @@ -44,52 +43,22 @@ module.exports = function(grunt) { files: ['README.md'], tasks: ['markdown'] }, - scripts: { - files: [ - 'js/models/*.js', - 'js/util/*.js', - 'js/plugins/*.js', - 'js/*.js', - '!js/copayBundle.js', - '!js/copayMain.js' - ], - tasks: ['exec:dev'] - }, css: { - files: ['css/src/*.css'], - tasks: ['cssmin:desktop'] + files: ['src/css/*.css'], + tasks: ['concat:css'] }, main: { files: [ - 'js/init.js', - 'js/app.js', - 'js/directives.js', - 'js/filters.js', - 'js/routes.js', - 'js/services/*.js', - 'js/controllers/*.js' + 'src/js/init.js', + 'src/js/app.js', + 'src/js/directives/*.js', + 'src/js/filters/*.js', + 'src/js/routes.js', + 'src/js/services/*.js', + 'src/js/models/*.js', + 'src/js/controllers/*.js' ], - tasks: ['concat:main'] - }, - config: { - files: ['config.js'], - tasks: ['exec:dev', 'concat:main'] - }, - test: { - files: ['test/**/*.js'], - tasks: ['mochaTest'] - } - }, - mochaTest: { - tests: { - options: { - require: 'setup/node.js', - reporter: 'spec', - mocha: require('mocha') - }, - src: [ - 'test/*.js', - ] + tasks: ['concat:js'] } }, markdown: { @@ -97,72 +66,80 @@ module.exports = function(grunt) { files: [{ expand: true, src: 'README.md', - dest: '.', + dest: './doc', ext: '.html' }] } }, concat: { - vendors: { - src: [ - 'lib/moment/min/moment.min.js', - 'lib/qrcode-generator/js/qrcode.js', - 'lib/lodash/dist/lodash.js', - 'lib/bitcore.js', - 'lib/file-saver/FileSaver.js', - 'lib/socket.io-client/socket.io.js', - 'lib/sjcl.js', - 'lib/qrcode-decoder-js/lib/qrcode-decoder.min.js', - 'lib/moment/lang/*.js' - ], - dest: 'lib/vendors.js' + options: { + sourceMap: false, + sourceMapStyle: 'link' // embed, link, inline }, angular: { src: [ - 'lib/angular/angular.min.js', - 'lib/angular-route/angular-route.min.js', - 'lib/angular-moment/angular-moment.js', - 'lib/angular-qrcode/qrcode.js', - 'lib/ng-idle/angular-idle.min.js', - 'lib/angular-foundation/mm-foundation.min.js', - 'lib/angular-foundation/mm-foundation-tpls.min.js', - 'lib/angular-gettext/dist/angular-gettext.min.js', - 'lib/angular-load/angular-load.min.js', - 'lib/angular-gravatar/build/md5.min.js', - 'lib/angular-gravatar/build/angular-gravatar.min.js', - 'lib/angular-touch/angular-touch.min.js' - // If you add libs here, remember to add it too to karma.conf + 'bower_components/fastclick/lib/fastclick.js', + 'bower_components/qrcode-generator/js/qrcode.js', + 'bower_components/qrcode-decoder-js/lib/qrcode-decoder.js', + 'bower_components/moment/min/moment-with-locales.js', + 'bower_components/angular/angular.js', + 'bower_components/angular-ui-router/release/angular-ui-router.js', + 'bower_components/angular-local-storage/dist/angular-local-storage.js', + 'bower_components/angular-foundation/mm-foundation.js', + 'bower_components/angular-foundation/mm-foundation-tpls.js', + 'bower_components/angular-animate/angular-animate.js', + 'bower_components/angular-moment/angular-moment.js', + 'bower_components/ng-lodash/build/ng-lodash.js', + 'bower_components/angular-qrcode/qrcode.js', + 'bower_components/angular-gettext/dist/angular-gettext.js', + 'bower_components/angular-touch/angular-touch.js', + 'bower_components/angular-bitcore-wallet-client/angular-bitcore-wallet-client.js', + 'bower_components/angular-ui-switch/angular-ui-switch.js' ], - dest: 'lib/angularjs-all.js' + dest: 'public/lib/angular.js' }, - main: { + js: { src: [ - 'js/app.js', - 'js/directives.js', - 'js/filters.js', - 'js/routes.js', - 'js/services/*.js', - 'js/controllers/*.js', - 'js/translations.js', - 'js/init.js', + 'src/js/app.js', + 'src/js/routes.js', + 'src/js/directives/*.js', + 'src/js/filters/*.js', + 'src/js/models/*.js', + 'src/js/services/*.js', + 'src/js/controllers/*.js', + 'src/js/translations.js', + 'src/js/version.js', + 'src/js/init.js' ], - dest: 'js/copayMain.js' + dest: 'public/js/copay.js' + }, + css: { + src: ['src/css/*.css'], + dest: 'public/css/copay.css' + }, + foundation: { + src: [ + 'bower_components/angular/angular-csp.css', + 'bower_components/foundation/css/foundation.css', + 'bower_components/animate.css/animate.css', + 'bower_components/angular-ui-switch/angular-ui-switch.css' + ], + dest: 'public/css/foundation.css', } }, cssmin: { - desktop: { + copay: { files: { - 'css/copay.min.css': ['css/src/*.css'], + 'public/css/copay.css': ['src/css/*.css'], } }, - mobile: { + foundation: { files: { - 'css/copay.min.css': ['css/src/*.css', '!css/src/desktop.css', '!css/src/animation.css'], - } - }, - vendors: { - files: { - 'css/vendors.min.css': ['css/foundation.min.css', 'css/foundation-icons.css'] + 'public/css/foundation.css': [ + 'bower_components/angular/angular-csp.css', + 'bower_components/foundation/css/foundation.css', + 'bower_components/animate.css/animate.css' + ] } } }, @@ -172,16 +149,15 @@ module.exports = function(grunt) { }, prod: { files: { - 'js/copayMain.js': ['js/copayMain.js'], - 'lib/angularjs-all.js': ['lib/angularjs-all.js'], - 'lib/vendors.js': ['lib/vendors.js'] + 'public/js/copay.js': ['public/js/copay.js'], + 'public/lib/angular.js': ['public/lib/angular.js'] } } }, nggettext_extract: { pot: { files: { - 'po/template.pot': ['index.html', 'views/*.html', 'views/**/*.html'] + 'po/template.pot': ['public/index.html', 'public/views/*.html', 'public/views/**/*.html'] } }, }, @@ -191,70 +167,58 @@ module.exports = function(grunt) { module: 'copayApp' }, files: { - 'js/translations.js': ['po/*.po'] + 'src/js/translations.js': ['po/*.po'] } }, }, copy: { - dist: { - files: [ - { - src: [ - 'index.html', - 'init.js', - 'config.js', - 'popup.html', - 'css/vendors.min.css', - 'css/copay.min.css', - 'js/copayBundle.js', - 'js/copayMain.js', - 'lib/vendors.js', - 'lib/angularjs-all.js', - 'font/**', - 'img/**', - 'sound/**', - 'views/**' - ], - dest: 'dist/web/' - } - ], - }, + icons: { + expand: true, + flatten: true, + src: 'bower_components/foundation-icon-fonts/foundation-icons.*', + dest: 'public/icons/' + } }, - jsdoc: { - dist: { - src: ['js/models/*.js', 'js/plugins/*.js'], - options: { - destination: 'doc', - configure: 'jsdoc.conf.json', - template: './node_modules/grunt-jsdoc/node_modules/ink-docstrap/template', - theme: 'flatly' - } + karma: { + unit: { + configFile: 'test/karma.conf.js' + }, + prod: { + configFile: 'test/karma.conf.js', + singleRun: true + } + }, + coveralls: { + options: { + debug: false, + coverageDir: 'coverage/report-lcov', + dryRun: true, + force: true, + recursive: false } } }); + grunt.loadNpmTasks('grunt-contrib-concat'); + grunt.loadNpmTasks('grunt-contrib-copy'); + grunt.loadNpmTasks('grunt-contrib-watch'); + grunt.loadNpmTasks('grunt-contrib-uglify'); + grunt.loadNpmTasks('grunt-contrib-cssmin'); + grunt.loadNpmTasks('grunt-angular-gettext'); + grunt.loadNpmTasks('grunt-markdown'); + grunt.loadNpmTasks('grunt-release'); + grunt.loadNpmTasks('grunt-exec'); + grunt.loadNpmTasks('grunt-karma'); + grunt.loadNpmTasks('grunt-karma-coveralls'); + grunt.registerTask('default', [ - 'exec:dev', 'nggettext_compile', 'concat', 'cssmin:desktop', 'cssmin:vendors' - ]); - grunt.registerTask('mobile', [ - 'exec:dev', 'nggettext_compile', 'concat', 'cssmin:mobile', 'cssmin:vendors' - ]); - grunt.registerTask('dist', [ - 'exec:prod', 'nggettext_compile', 'concat', 'cssmin:desktop', 'cssmin:vendors', 'uglify', 'copy:dist' - ]); - grunt.registerTask('dist-dbg', [ - 'exec:prod', 'nggettext_compile', 'concat', 'cssmin:desktop', 'cssmin:vendors', 'copy:dist' - ]); - grunt.registerTask('dist-mobile', [ - 'exec:prod', 'nggettext_compile', 'concat', 'cssmin:mobile', 'cssmin:vendors', 'uglify', 'copy:dist' - ]); - grunt.registerTask('dist-mobile-dbg', [ - 'exec:dev', 'nggettext_compile', 'concat', 'cssmin:mobile', 'cssmin:vendors', 'copy:dist' + 'nggettext_compile', 'exec:version', 'concat', 'copy' ]); grunt.registerTask('prod', [ - 'exec:prod', 'nggettext_compile', 'concat', 'cssmin:desktop', 'cssmin:vendors', 'uglify' + 'default', 'uglify' ]); grunt.registerTask('translate', ['nggettext_extract']); - grunt.registerTask('docs', ['jsdoc']); + grunt.registerTask('test', ['karma:unit']); + grunt.registerTask('test-coveralls', ['karma:prod', 'coveralls']); }; diff --git a/Makefile b/Makefile index 25563803c..2fc90c1a7 100644 --- a/Makefile +++ b/Makefile @@ -21,8 +21,8 @@ cordova-base: # release-android: cordova-base # make -C cordova release-android # -wp8: - cordova/build.sh WP8 +wp8-prod: + cordova/build.sh WP8 --clear cordova/wp/fix-svg.sh echo -e "\a" @@ -31,7 +31,7 @@ wp8-debug: cordova/wp/fix-svg.sh echo -e "\a" -ios: +ios-prod: cordova/build.sh IOS --clear cd cordova/project && cordova build ios open cordova/project/platforms/ios/Copay.xcodeproj @@ -41,10 +41,14 @@ ios-debug: cd cordova/project && cordova build ios open cordova/project/platforms/ios/Copay.xcodeproj -android: - cordova/build.sh ANDROID --dbgjs --clear - cd cordova/project && cordova run android - android-prod: cordova/build.sh ANDROID --clear cd cordova/project && cordova build android --release + +android-debug: + cordova/build.sh ANDROID --dbgjs --clear + cd cordova/project && cordova run android + +android-debug-fast: + cordova/build.sh ANDROID --dbgjs + cd cordova/project && cordova run android diff --git a/app.js b/app.js index dcdb86880..b4909fd2d 100644 --- a/app.js +++ b/app.js @@ -1,54 +1,13 @@ var express = require('express'); -var http = require('http'); +var path = require('path'); var app = express(); +app.use(express.static(__dirname + '/public')); -// This is not really necesarry, just to simulate -// Content Security Policy from Google Chrome Apps. -// -// app.use(function(req, res, next){ -// res.header("Content-Security-Policy", "script-src 'self';object-src 'none';media-src 'self';frame-src 'none';font-src 'self' data:"); -// next(); -// }); +var port = process.env.PORT || 3000; +app.listen(port); +console.log("App listening on port " + port); -app.use('/', express.static(__dirname + '/')); app.get('*', function(req, res) { - return res.sendFile(__dirname + '/' + 'index.html'); + res.sendFile(path.join(__dirname, 'public')); }); -app.start = function(port, callback) { - - app.set('port', port); - app.use(express.static(__dirname)); - - if (process.env.USE_HTTPS) { - var path = require('path'); - - var bc = path.dirname(require.resolve('bitcore/package.json')); - var pserver = require(bc + '/examples/PayPro/server.js'); - - pserver.removeListener('request', pserver.app); - - // pserver.options['no-tx'] = true; - // pserver.options['discovery'] = true; - - 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; - } - - app.listen(port, function() { - callback('http://localhost:' + port); - }); -}; - -module.exports = app; - diff --git a/TODO.md b/atom-shell.md similarity index 98% rename from TODO.md rename to atom-shell.md index bc9a34c43..0807918fb 100644 --- a/TODO.md +++ b/atom-shell.md @@ -1,3 +1,8 @@ + +# Warning + +This NEEDS to be updated. + ## Running in the Native Shell Copay can be executed from within a "native" application shell, providing some diff --git a/bower.json b/bower.json index 31ac3bc02..332ae295a 100644 --- a/bower.json +++ b/bower.json @@ -3,19 +3,23 @@ "keywords": [ "copay", "wallet", - "multisignature" + "multisignature", + "bircore" ], "dependencies": { - "angular": "~1.3.0", + "angular": "~1.3.13", "angular-foundation": "*", - "angular-route": "~1.3.0", - "angular-qrcode": "~3.1.0", - "angular-mocks": "~1.3.0", - "angular-gettext": "~1.1.0", - "mocha": "~1.18.2", - "chai": "~1.9.1", - "sjcl": "1.0.0", - "file-saver": "*", + "angular-animate": "~1.3.13", + "angular-touch": "~1.3.0", + "angular-qrcode": "~5.1.0", + "angular-gettext": "~2.0.5", + "animate.css": "~3.2.0", + "foundation": "zurb/bower-foundation#~5.5.1", + "foundation-icon-fonts": "*", + "ng-lodash": "~0.2.0", + "angular-moment": "~0.9.0", + "angular-bitcore-wallet-client": "^0.0.14", + "angular-ui-router": "~0.2.13", "qrcode-decoder-js": "*", "angular-moment": "~0.7.1", "socket.io-client": ">=1.0.0", @@ -26,6 +30,6 @@ "angular-touch": "~1.3.0" }, "resolutions": { - "angular": "~1.3.0" + "qrcode-generator": "0.0.1" } } diff --git a/browser-extensions/chrome/README.md b/browser-extensions/chrome/README.md deleted file mode 100644 index 0eec88a19..000000000 --- a/browser-extensions/chrome/README.md +++ /dev/null @@ -1,9 +0,0 @@ -Development - -* Just run: - -``` -$ sh chrome/build.sh -``` - -* The ZIP file is *chrome/copay-chrome-extension.zip* diff --git a/browser-extensions/chrome/build.sh b/browser-extensions/chrome/build.sh deleted file mode 100755 index 211de01f9..000000000 --- a/browser-extensions/chrome/build.sh +++ /dev/null @@ -1,64 +0,0 @@ -#! /bin/bash - -# Description: This script compiles and copy the needed files to later package the application for Chrome - -OpenColor="\033[" -Red="1;31m" -Yellow="1;33m" -Green="1;32m" -CloseColor="\033[0m" - -# Check function OK -checkOK() { - if [ $? != 0 ]; then - echo "${OpenColor}${Red}* ERROR. Exiting...${CloseColor}" - exit 1 - fi -} - -# Configs -BUILDDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" -APPDIR="$BUILDDIR/copay-chrome-extension" -ZIPFILE="copay-chrome-extension.zip" -VERSION=`cut -d '"' -f2 $BUILDDIR/../../version.js|head -n 1` - -# Move to the build directory -cd $BUILDDIR - -# Create/Clean temp dir -echo "${OpenColor}${Green}* Checking temp dir...${CloseColor}" -if [ -d $APPDIR ]; then - rm -rf $APPDIR -fi - -mkdir -p $APPDIR - -# Re-compile copayBundle.js -echo "${OpenColor}${Green}* Generating copay bundle...${CloseColor}" -grunt prod -checkOK - -# Copy all chrome-extension files -echo "${OpenColor}${Green}* Copying all chrome-extension files...${CloseColor}" -sed "s/APP_VERSION/$VERSION/g" manifest.json > $APPDIR/manifest.json -checkOK - - -INCLUDE=`cat ../include` -cd $BUILDDIR/../.. -LIBS=`cat index.html |grep -o -E 'src="([^"#]+)"' | cut -d'"' -f2|grep lib` -echo "LIBS: $LIBS" - -CMD="rsync -rLRv --exclude-from $BUILDDIR/../exclude $INCLUDE $LIBS $APPDIR" -echo $CMD -$CMD -checkOK - -# Zipping chrome-extension -echo "${OpenColor}${Green}* Zipping all chrome-extension files...${CloseColor}" -cd $BUILDDIR -rm $ZIPFILE -zip -qr $ZIPFILE "`basename $APPDIR`" -checkOK - -echo "${OpenColor}${Yellow}\nThe Chrome Extension is ready at $BUILDDIR/copay-chrome-extension.zip${CloseColor}" diff --git a/browser-extensions/chrome/initial.js b/browser-extensions/chrome/initial.js deleted file mode 100644 index fc12301b5..000000000 --- a/browser-extensions/chrome/initial.js +++ /dev/null @@ -1,8 +0,0 @@ -chrome.app.runtime.onLaunched.addListener(function() { - chrome.app.window.create('index.html', { - 'bounds': { - 'width': 1200, - 'height': 800 - } - }); -}); diff --git a/browser-extensions/chrome/manifest.json b/browser-extensions/chrome/manifest.json deleted file mode 100644 index d5361b40e..000000000 --- a/browser-extensions/chrome/manifest.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "manifest_version": 2, - "name": "Copay", - "description": "A secure Bitcoin wallet for friends and companies", - "version": "APP_VERSION", - "permissions": [ - "storage", - "notifications", - "videoCapture" - ], - "app": { - "background": { - "scripts": ["initial.js"] - } - }, - "icons": { - "128": "img/icons/icon.png" - } -} diff --git a/browser-extensions/chrome/tile.png b/browser-extensions/chrome/tile.png deleted file mode 100644 index d457fa2bf6a1dcb1b3acb26efb1d34f03fb2fad3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 30719 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 nIOIF0I)oyC*^>IS`*&_;um diff --git a/browser-extensions/exclude b/browser-extensions/exclude deleted file mode 100644 index 3689c0576..000000000 --- a/browser-extensions/exclude +++ /dev/null @@ -1,19 +0,0 @@ -lib/socket.io -lib/*/test -lib/*/demo -lib/sjcl/ -lib/angular/angular.js -lib/moment/lang -lib/moment/min/*lang* -lib/moment/moment.js -lib/angular/angular.min.js.gzip -lib/bitcore/node_modules -lib/bitcore/.git -lib/bitcore/docs -lib/bitcore/lib -lib/bitcore/examples -lib/bitcore/coverage -lib/bitcore/build -.git -tests -.* diff --git a/browser-extensions/include b/browser-extensions/include deleted file mode 100644 index 5a43929ed..000000000 --- a/browser-extensions/include +++ /dev/null @@ -1,16 +0,0 @@ -css -font -img -js -sound -views -config.js -init.js -initial.js -version.js -index.html -popup.html -lib/angular/angular-csp.css -lib/angular/angular.min.js.map -lib/angular-route/angular-route.min.js.map -lib/angular-touch/angular-touch.min.js.map diff --git a/config.js b/config.js deleted file mode 100644 index 27f77de49..000000000 --- a/config.js +++ /dev/null @@ -1,96 +0,0 @@ -'use strict'; -var defaultConfig = { - // DEFAULT network (livenet or testnet) - networkName: 'livenet', - logLevel: 'info', - - - // wallet limits - limits: { - totalCopayers: 6, - mPlusN: 100, - }, - - // network layer config - network: { - testnet: { - url: 'https://test-insight.bitpay.com:443', - transports: ['polling'], - }, - livenet: { - url: 'https://insight.bitpay.com:443', - transports: ['polling'], - }, - }, - - // wallet default config - wallet: { - requiredCopayers: 2, - totalCopayers: 3, - spendUnconfirmed: true, - reconnectDelay: 5000, - idleDurationMin: 4, - settings: { - unitName: 'bits', - unitToSatoshi: 100, - unitDecimals: 2, - alternativeName: 'US Dollar', - alternativeIsoCode: 'USD', - } - }, - - // local encryption/security config - passphraseConfig: { - iterations: 5000, - storageSalt: 'mjuBtGybi/4=', - }, - - rates: { - url: 'https://insight.bitpay.com:443/api/rates', - }, - - verbose: 1, - - plugins: { - //LocalStorage: true, - // EncryptedLocalStorage: true, - //GoogleDrive: true, - //InsightStorage: true - EncryptedInsightStorage: true, - }, - - // This can be changed on the UX > Settings > Insight livenet - EncryptedInsightStorage: { - url: 'https://insight.bitpay.com:443/api/email', - //url: 'http://localhost:3001/api/email' - - // This KDF parameters are for the passphrase for Insight authentication - // Are not related to encryption itself. - // - // WARN: Changing this parameters would prevent accesing previously created profiles. - iterations: 1000, - salt: 'jBbYTj8zTrOt6V', - }, - - minPasswordStrength: 4, - - /* - GoogleDrive: { - home: 'copay', - - - // This clientId was generated at: - // https://console.developers.google.com/project - // To run Copay with Google Drive at your domain you need - // to generata your own Id. - // for localhost:3001 you can use you can: - // - clientId: '232630733383-a35gcnovnkgka94394i88gq60vtjb4af.apps.googleusercontent.com', - - // for copay.io: - // clientId: '1036948132229-biqm3b8sirik9lt5rtvjo9kjjpotn4ac.apps.googleusercontent.com', - }, - */ -}; -if (typeof module !== 'undefined') - module.exports = defaultConfig; diff --git a/copay.js b/copay.js deleted file mode 100644 index 462ad98da..000000000 --- a/copay.js +++ /dev/null @@ -1,26 +0,0 @@ -// core -module.exports.PublicKeyRing = require('./js/models/PublicKeyRing'); -module.exports.TxProposal = require('./js/models/TxProposal'); -module.exports.TxProposals = require('./js/models/TxProposals'); -module.exports.PrivateKey = require('./js/models/PrivateKey'); -module.exports.HDPath = require('./js/models/HDPath'); -module.exports.HDParams = require('./js/models/HDParams'); -module.exports.Async = require('./js/models/Async'); -module.exports.Insight = require('./js/models/Insight'); -module.exports.RateService = require('./js/models/RateService'); -module.exports.Identity = require('./js/models/Identity'); -module.exports.Wallet = require('./js/models/Wallet'); -module.exports.Compatibility = require('./js/models/Compatibility'); -module.exports.PluginManager = require('./js/models/PluginManager'); - - -module.exports.crypto = require('./js/util/crypto'); -module.exports.logger = require('./js/util/log'); -module.exports.csv = require('./js/util/csv'); - -module.exports.version = require('./version').version; -module.exports.commitHash = require('./version').commitHash; - - -module.exports.defaultConfig = require('./config'); - diff --git a/cordova/android/AndroidManifest.xml b/cordova/android/AndroidManifest.xml index c02f3e726..fb5de590d 100644 --- a/cordova/android/AndroidManifest.xml +++ b/cordova/android/AndroidManifest.xml @@ -1,5 +1,5 @@ - + @@ -9,7 +9,7 @@ - + diff --git a/cordova/build.sh b/cordova/build.sh index cc4b684ed..6ae36addc 100755 --- a/cordova/build.sh +++ b/cordova/build.sh @@ -15,7 +15,6 @@ checkOK() { # Configs BUILDDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" PROJECT="$BUILDDIR/project" -VERSION=`cut -d '"' -f2 $BUILDDIR/../version.js` CURRENT_OS=$1 @@ -115,27 +114,39 @@ if [ ! -d $PROJECT ]; then cordova plugin add hu.dpal.phonegap.plugins.spinnerdialog checkOK + cordova plugin add org.apache.cordova.dialogs + checkOK + + cordova plugin add org.apache.cordova.network-information + checkOK + + cordova plugin add org.apache.cordova.console + checkok + + cordova plugin add hu.dpal.phonegap.plugins.uniquedeviceid + checkok + fi if $DBGJS then echo "${OpenColor}${Green}* Generating copay bundle (debug js)...${CloseColor}" cd $BUILDDIR/.. - grunt dist-mobile-dbg + grunt checkOK else echo "${OpenColor}${Green}* Generating copay bundle...${CloseColor}" cd $BUILDDIR/.. - grunt dist-mobile + grunt prod checkOK fi echo "${OpenColor}${Green}* Copying files...${CloseColor}" cd $BUILDDIR/.. -cp -af dist/web/** $PROJECT/www +cp -af public/** $PROJECT/www checkOK -sed "s/<\!-- PLACEHOLDER: CORDOVA SRIPT -->/ - - - - - - - - - - - - diff --git a/init.js b/init.js deleted file mode 100644 index c56005eb9..000000000 --- a/init.js +++ /dev/null @@ -1,29 +0,0 @@ -var isChromeApp = window.chrome && chrome.runtime && chrome.runtime.id; -var ld; -if (!isChromeApp) { - ld = (document.all); -} - -var ns4 = document.layers; -var ns6 = !isChromeApp && document.getElementById && !document.all; -var ie4 = !isChromeApp && document.all; -if (ns4) { - ld = document.loading; -} else if (ns6) { - ld = document.getElementById("loading").style; -} else if (ie4) { - ld = document.all.loading.style; -} - -function init() { - if (ns4) { - ld.visibility = "hidden"; - } else if (ns6 || ie4) { - ld.display = "none"; - } else { - ld = document.getElementById("loading").style; - ld.visibility = "hidden"; - ld.display = "none"; - } -} -init(); diff --git a/initial.js b/initial.js deleted file mode 100644 index fc12301b5..000000000 --- a/initial.js +++ /dev/null @@ -1,8 +0,0 @@ -chrome.app.runtime.onLaunched.addListener(function() { - chrome.app.window.create('index.html', { - 'bounds': { - 'width': 1200, - 'height': 800 - } - }); -}); diff --git a/js/app.js b/js/app.js deleted file mode 100644 index 0f8d9bbec..000000000 --- a/js/app.js +++ /dev/null @@ -1,69 +0,0 @@ -'use strict'; - -var copay = require('copay'); -var _ = require('lodash'); -var LS = require('../js/plugins/LocalStorage'); -var ls = new LS(); - - -// TODO move this to configService ! -var config = copay.defaultConfig; - -ls.getItem('config', function(err, data) { - var localConfig; - try { - localConfig = JSON.parse(data); - } catch(e) {}; - if (localConfig) { - var cmv = copay.version.split('.')[1]; - var lmv = localConfig.version ? localConfig.version.split('.')[1] : '-1'; - if (cmv === lmv) { - _.each(localConfig, function(value, key) { - config[key] = value; - }); - } - } -}); - -var modules = [ - 'ngRoute', - 'angularMoment', - 'mm.foundation', - 'monospaced.qrcode', - 'ngIdle', - 'gettext', - 'ui.gravatar', - 'ngTouch', - 'copayApp.filters', - 'copayApp.services', - 'copayApp.controllers', - 'copayApp.directives', -]; - -var copayApp = window.copayApp = angular.module('copayApp', modules); - -var defaults = JSON.parse(JSON.stringify(copay.defaultConfig)); -copayApp.value('defaults', defaults); - -copayApp.config(function($sceDelegateProvider) { - $sceDelegateProvider.resourceUrlWhitelist([ - 'self', - 'mailto:**' - ]); -}); - -angular.module('ui.gravatar').config([ - 'gravatarServiceProvider', - function(gravatarServiceProvider) { - gravatarServiceProvider.defaults = { - size: 35 - }; - // Use https endpoint - gravatarServiceProvider.secure = true; - } -]); - -angular.module('copayApp.filters', []); -angular.module('copayApp.services', []); -angular.module('copayApp.controllers', []); -angular.module('copayApp.directives', []); diff --git a/js/controllers/copayers.js b/js/controllers/copayers.js deleted file mode 100644 index 877efd6f0..000000000 --- a/js/controllers/copayers.js +++ /dev/null @@ -1,72 +0,0 @@ -'use strict'; - -angular.module('copayApp.controllers').controller('CopayersController', - function($scope, $rootScope, $timeout, go, identityService, notification, isCordova) { - var w = $rootScope.wallet; - - - $scope.init = function() { - $rootScope.title = 'Share this secret with your copayers'; - $scope.loading = false; - $scope.secret = $rootScope.wallet.getSecret(); - $scope.isCordova = isCordova; - - w.on('publicKeyRingUpdated', $scope.updateList); - w.on('ready', $scope.updateList); - - $scope.updateList(); - }; - - $scope.updateList = function() { - var w = $rootScope.wallet; - - $scope.copayers = $rootScope.wallet.getRegisteredPeerIds(); - if (w.isComplete()) { - - w.removeListener('publicKeyRingUpdated', $scope.updateList); - w.removeListener('ready', $scope.updateList); - go.walletHome(); - } - $timeout(function() { - $rootScope.$digest(); - }, 1); - }; - - $scope.deleteWallet = function() { - $rootScope.starting = true; - $timeout(function() { - identityService.deleteWallet(w, function(err) { - $rootScope.starting = false; - if (err) { - $scope.error = err.message || err; - copay.logger.warn(err); - $timeout(function () { $scope.$digest(); }); - } else { - if ($rootScope.wallet) { - go.walletHome(); - } - $timeout(function() { - notification.success('Success', 'The wallet "' + (w.name || w.id) + '" was deleted'); - }); - } - }); - }, 100); - }; - - $scope.copySecret = function(secret) { - if (isCordova) { - window.cordova.plugins.clipboard.copy(secret); - window.plugins.toast.showShortCenter('Copied to clipboard'); - } - }; - - $scope.shareSecret = function(secret) { - if (isCordova) { - if (isMobile.Android() || isMobile.Windows()) { - window.ignoreMobilePause = true; - } - window.plugins.socialsharing.share(secret, null, null, null); - } - }; - - }); diff --git a/js/controllers/create.js b/js/controllers/create.js deleted file mode 100644 index 3e65e7d34..000000000 --- a/js/controllers/create.js +++ /dev/null @@ -1,78 +0,0 @@ -'use strict'; - -angular.module('copayApp.controllers').controller('CreateController', - function($scope, $rootScope, $location, $timeout, identityService, backupService, notification, defaults, isMobile, isCordova) { - - $rootScope.fromSetup = true; - $scope.loading = false; - $scope.walletPassword = $rootScope.walletPassword; - $scope.isMobile = isMobile.any(); - $scope.hideAdv = true; - $scope.networkName = config.networkName; - $rootScope.title = 'Create new wallet'; - $rootScope.hideWalletNavigation = true; - $scope.isWindowsPhoneApp = isMobile.Windows() && isCordova; - - // ng-repeat defined number of times instead of repeating over array? - $scope.getNumber = function(num) { - return new Array(num); - } - - $scope.totalCopayers = config.wallet.totalCopayers; - $scope.TCValues = _.range(1, config.limits.totalCopayers + 1); - - var updateRCSelect = function(n) { - var maxReq = copay.Wallet.getMaxRequiredCopayers(n); - $scope.RCValues = _.range(1, maxReq + 1); - $scope.requiredCopayers = Math.min(parseInt(n / 2 + 1), maxReq); - }; - - updateRCSelect($scope.totalCopayers); - - $scope.$watch('totalCopayers', function(tc) { - updateRCSelect(tc); - }); - - $scope.$watch('networkName', function(tc) { - $scope.networkUrl = config.network[$scope.networkName].url; - }); - - $scope.showNetwork = function() { - return $scope.networkUrl != defaults.network.livenet.url && $scope.networkUrl != defaults.network.testnet.url; - }; - - - $scope.create = function(form) { - if (form && form.$invalid) { - $scope.error = 'Please enter the required fields'; - return; - } - var opts = { - requiredCopayers: $scope.requiredCopayers, - totalCopayers: $scope.totalCopayers, - name: $scope.walletName, - privateKeyHex: $scope.private, - networkName: $scope.networkName, - }; - $rootScope.starting = true; - identityService.createWallet(opts, function(err, wallet){ - $rootScope.starting = false; - if (err || !wallet) { - copay.logger.debug(err); - if (err.match('OVERQUOTA')){ - $scope.error = 'Could not create wallet: storage limits on remove server exceeded'; - } else { - $scope.error = 'Could not create wallet: ' + err; - } - } - - $timeout(function(){ - $rootScope.$digest(); - },1); - }); - }; - - $scope.$on("$destroy", function () { - $rootScope.hideWalletNavigation = false; - }); - }); diff --git a/js/controllers/createProfile.js b/js/controllers/createProfile.js deleted file mode 100644 index 1c54a2d08..000000000 --- a/js/controllers/createProfile.js +++ /dev/null @@ -1,202 +0,0 @@ -'use strict'; - -angular.module('copayApp.controllers').controller('CreateProfileController', function($scope, $rootScope, $location, $timeout, $window, notification, pluginManager, identityService, pinService, isMobile, isCordova, configService, go) { - - var _credentials; - - $scope.init = function() { - - if ($rootScope.wallet) - go.walletHome(); - - $scope.isMobile = isMobile.any(); - $scope.isWindowsPhoneApp = isMobile.Windows() && isCordova; - $scope.hideForWP = 0; - $scope.digits = []; - $scope.defined = []; - $scope.askForPin = 0; - - $scope.createStep = 'storage'; - $scope.useLocalstorage = false; - $scope.minPasswordStrength = _.isUndefined(config.minPasswordStrength) ? - 4 : config.minPasswordStrength; - - }; - - $scope.clear = function() { - pinService.clearPin($scope); - }; - - $scope.press = function(digit) { - pinService.pressPin($scope, digit, true); - }; - - $scope.skip = function () { - pinService.skipPin($scope, true); - }; - - $scope.formFocus = function() { - if (!$scope.isWindowsPhoneApp) return - $scope.hideForWP = true; - $timeout(function() { - $scope.$digest(); - }, 1); - }; - - $scope.createPin = function(pin) { - preconditions.checkArgument(pin); - preconditions.checkState($rootScope.iden); - preconditions.checkState(_credentials && _credentials.email); - $rootScope.starting = true; - - $timeout(function() { - pinService.save(pin, _credentials.email, _credentials.password, function(err) { - _credentials.password = ''; - _credentials = null; - $scope.askForPin = 0; - $rootScope.hasPin = true; - $scope.createDefaultWallet(); - }); - }, 100); - }; - - - $scope.setStep = function(step) { - $scope.error = null; - $scope.createStep = step; - $scope.hideForWP = false; - $timeout(function() { - $scope.$digest(); - }, 1); - }; - - $scope.selectStorage = function(storage) { - $scope.useLocalstorage = storage == 'local'; - $scope.hideForWP = false; - $timeout(function() { - $scope.$digest(); - }, 1); - }; - - $scope.goToEmail = function() { - $scope.createStep = 'email'; - $scope.useEmail = !$scope.useLocalstorage; - }; - - $scope.setEmailOrUsername = function(form) { - $scope.userOrEmail = $scope.useLocalstorage ? form.username.$modelValue : form.email.$modelValue; - preconditions.checkState($scope.userOrEmail); - - $scope.error = null; - $scope.hideForWP = false; - $scope.createStep = 'pass'; - $timeout(function() { - $scope.$digest(); - }, 1); - }; - - /* Last step. Will emit after creation so the UX gets updated */ - $scope.createDefaultWallet = function() { - $rootScope.hideNavigation = false; - $rootScope.starting = true; - identityService.createDefaultWallet(function(err) { - $scope.askForPin = 0; - $rootScope.starting = null; - - if (err) { - var msg = err.toString(); - $scope.error = msg; - } else { - if (!$scope.useLocalstorage) { - $rootScope.pleaseConfirmEmail = true; - } - } - - }); - }; - - $scope._doCreateProfile = function(emailOrUsername, password, cb) { - preconditions.checkArgument(_.isString(emailOrUsername)); - preconditions.checkArgument(_.isString(password)); - - $rootScope.hideNavigation = false; - - identityService.create(emailOrUsername, password, function(err) { - if (err) { - var msg = err.toString(); - $scope.createStep = 'email'; - if (msg.indexOf('EEXIST') >= 0 || msg.indexOf('BADC') >= 0) { - msg = 'This profile already exists' - } - if (msg.indexOf('EMAILERROR') >= 0) { - msg = 'Could not send verification email. Please check your email address.'; - } - return cb(msg); - } else { - // mobile - if ($scope.isMobile) { - $rootScope.starting = null; - _credentials = { - email: emailOrUsername, - password: password, - }; - $scope.askForPin = 1; - $scope.hideForWP = 0; - - $rootScope.hideNavigation = true; - $timeout(function() { - $rootScope.$digest(); - }, 1); - return; - } else { - $scope.createDefaultWallet(); - } - } - return cb(); - }); - }; - - - $scope.saveSettings = function(cb) { - var plugins = config.plugins; - - plugins.EncryptedLocalStorage = false; - plugins.EncryptedInsightStorage = false; - - var pluginName = $scope.useLocalstorage ? 'EncryptedLocalStorage' : 'EncryptedInsightStorage'; - plugins[pluginName] = true; - - configService.set({ - plugins: plugins - }, cb); - }; - - - $scope.createProfile = function(form) { - if (form && form.$invalid) { - $scope.error = 'Please enter the required fields'; - return; - } - - $scope.saveSettings(function(err) { - preconditions.checkState(!err, err); - $rootScope.starting = true; - - $scope._doCreateProfile($scope.userOrEmail, form.password.$modelValue, function(err) { - if (err) { - $scope.error = err; - $scope.passwordStrength = null; - $rootScope.starting = false; - } - form.password.$setViewValue(''); - form.password.$render(); - form.repeatpassword.$setViewValue(''); - form.repeatpassword.$render(); - form.$setPristine(); - $timeout(function() { - $rootScope.$digest(); - }, 1); - }); - }); - }; -}); diff --git a/js/controllers/history.js b/js/controllers/history.js deleted file mode 100644 index f6effad96..000000000 --- a/js/controllers/history.js +++ /dev/null @@ -1,204 +0,0 @@ -'use strict'; -var bitcore = require('bitcore'); - -angular.module('copayApp.controllers').controller('HistoryController', - function($scope, $rootScope, $filter, $timeout, $modal, rateService, notification, go) { - var w = $rootScope.wallet; - - $rootScope.title = 'History'; - $scope.loading = false; - $scope.generating = false; - $scope.lastShowed = false; - - $scope.currentPage = 1; - $scope.itemsPerPage = 10; - $scope.nbPages = 0; - $scope.totalItems = 0; - $scope.blockchain_txs = []; - $scope.alternativeCurrency = []; - - $scope.selectPage = function(page) { - $scope.paging = true; - $scope.currentPage = page; - $scope.update(); - }; - - $scope.downloadHistory = function() { - var w = $rootScope.wallet; - if (!w) return; - - var filename = "copay_history.csv"; - var descriptor = { - columns: [{ - label: 'Date', - property: 'ts', - type: 'date' - }, { - label: 'Amount (' + w.settings.unitName + ')', - property: 'amount', - type: 'number' - }, { - label: 'Amount (' + w.settings.alternativeIsoCode + ')', - property: 'alternativeAmount' - }, { - label: 'Action', - property: 'action' - }, { - label: 'AddressTo', - property: 'addressTo' - }, { - label: 'Comment', - property: 'comment' - }, ], - }; - if (w.isShared()) { - descriptor.columns.push({ - label: 'Signers', - property: function(obj) { - if (!obj.actionList) return ''; - return _.map(obj.actionList, function(action) { - return w.publicKeyRing.nicknameForCopayer(action.cId); - }).join('|'); - } - }); - } - - $scope.generating = true; - - $scope._getTransactions(w, null, function(err, res) { - if (err) { - $scope.generating = false; - logger.error(err); - notification.error('Could not get transaction history'); - return; - } - $scope._addRates(w, res.items, function(err) { - copay.csv.toCsv(res.items, descriptor, function(err, res) { - if (err) { - $scope.generating = false; - logger.error(err); - notification.error('Could not generate csv file'); - return; - } - var csvContent = "data:text/csv;charset=utf-8," + res; - var encodedUri = encodeURI(csvContent); - var link = document.createElement("a"); - link.setAttribute("href", encodedUri); - link.setAttribute("download", filename); - link.click(); - $scope.generating = false; - $scope.$digest(); - }); - }); - }); - }; - - - $scope.update = function() { - $scope.getTransactions(); - }; - - $scope.show = function() { - $scope.loading = true; - setTimeout(function() { - $scope.update(); - }, 1); - }; - - $scope._getTransactions = function(w, opts, cb) { - w.getTransactionHistory(opts, function(err, res) { - if (err) return cb(err); - if (!res) return cb(); - - var now = new Date(); - var items = res.items; - _.each(items, function(tx) { - tx.ts = tx.minedTs || tx.sentTs; - tx.rateTs = Math.floor((tx.ts || now) / 1000); - tx.amount = $filter('noFractionNumber')(tx.amount); - }); - return cb(null, res); - }); - }; - - $scope._addRates = function(w, txs, cb) { - if (!txs || txs.length == 0) return cb(); - var index = _.groupBy(txs, 'rateTs'); - rateService.getHistoricRates(w.settings.alternativeIsoCode, _.keys(index), function(err, res) { - if (err || !res) return cb(err); - _.each(res, function(r) { - _.each(index[r.ts], function (tx) { - var alternativeAmount = (r.rate != null ? tx.amountSat * rateService.SAT_TO_BTC * r.rate : null); - tx.alternativeAmount = alternativeAmount ? $filter('noFractionNumber')(alternativeAmount, 2) : null; - }); - }); - return cb(); - }); - }; - - - $scope.openTxModal = function(btx) { - var ModalInstanceCtrl = function($scope, $modalInstance) { - $scope.btx = btx; - - $scope.getShortNetworkName = function() { - var w = $rootScope.wallet; - return w.getNetworkName().substring(0, 4); - }; - - $scope.cancel = function() { - $modalInstance.dismiss('cancel'); - }; - }; - - $modal.open({ - templateUrl: 'views/modals/tx-details.html', - windowClass: 'medium', - controller: ModalInstanceCtrl, - }); - }; - - $scope.getTransactions = function() { - var w = $rootScope.wallet; - if (!w) return; - - $scope.blockchain_txs = w.cached_txs || []; - $scope.loading = true; - - $scope._getTransactions(w, { - currentPage: $scope.currentPage, - itemsPerPage: $scope.itemsPerPage, - }, function(err, res) { - if (err) throw err; - - if (!res) { - $scope.loading = false; - $scope.lastShowed = false; - return; - } - - var items = res.items; - $scope._addRates(w, items, function(err) { - $timeout(function() { - $scope.$digest(); - }, 1); - }) - - $scope.blockchain_txs = w.cached_txs = items; - $scope.nbPages = res.nbPages; - $scope.totalItems = res.nbItems; - - $scope.loading = false; - $scope.paging = false; - setTimeout(function() { - $scope.$digest(); - }, 1); - }); - }; - - - $scope.hasAction = function(actions, action) { - return actions.hasOwnProperty('create'); - }; - - }); diff --git a/js/controllers/home.js b/js/controllers/home.js deleted file mode 100644 index 5dd6f1b2a..000000000 --- a/js/controllers/home.js +++ /dev/null @@ -1,216 +0,0 @@ -'use strict'; - -angular.module('copayApp.controllers').controller('HomeController', function($scope, $rootScope, $timeout, $window, go, notification, identityService, Compatibility, pinService, applicationService, isMobile, isCordova, localstorageService) { - - var KEY = 'CopayDisclaimer'; - var ls = localstorageService; - var _credentials; - - $scope.init = function() { - $scope.isMobile = isMobile.any(); - $scope.isWindowsPhoneApp = isMobile.Windows() && isCordova; - $scope.hideForWP = 0; - $scope.attempt = 0; - $scope.digits = []; - $scope.defined = []; - $scope.askForPin = 0; - - // This is only for backwards compat, insight api should link to #!/confirmed directly - if (getParam('confirmed')) { - var hashIndex = window.location.href.indexOf('/?'); - window.location = window.location.href.substr(0, hashIndex) + '#!/confirmed'; - return; - } - - if ($rootScope.fromEmailConfirmation) { - $scope.confirmedEmail = true; - $rootScope.fromEmailConfirmation = false; - } - - if ($rootScope.wallet) { - go.walletHome(); - } - - Compatibility.check($scope); - pinService.check(function(err, value) { - $rootScope.hasPin = value; - }); - $scope.usingLocalStorage = config.plugins.EncryptedLocalStorage; - - if (isCordova) { - ls.getItem(KEY, function(err, value) { - $scope.showDisclaimer = value ? null : true; - }); - } - }; - - $scope.clear = function() { - pinService.clearPin($scope); - }; - - $scope.press = function(digit) { - pinService.pressPin($scope, digit); - }; - - $scope.skip = function () { - pinService.skipPin($scope); - }; - - $scope.agreeDisclaimer = function() { - ls.setItem(KEY, true, function(err) { - $scope.showDisclaimer = null; - }); - }; - - $scope.formFocus = function() { - if ($scope.isWindowsPhoneApp) { - $scope.hideForWP = true; - $timeout(function() { - $scope.$digest(); - }, 1); - } - }; - - $scope.openWithPin = function(pin) { - - if (!pin) { - $scope.error = 'Please enter the required fields'; - return; - } - $rootScope.starting = true; - - $timeout(function() { - var credentials = pinService.get(pin, function(err, credentials) { - if (err || !credentials) { - $rootScope.starting = null; - $scope.error = 'Wrong PIN'; - $scope.clear(); - $timeout(function() { - $scope.error = null; - }, 2000); - return; - } - $scope.open(credentials.email, credentials.password); - }); - }, 100); - }; - - $scope.openWallets = function() { - preconditions.checkState($rootScope.iden); - var iden = $rootScope.iden; - $rootScope.hideNavigation = false; - $rootScope.starting = true; - iden.openWallets(); - }; - - $scope.createPin = function(pin) { - preconditions.checkArgument(pin); - preconditions.checkState($rootScope.iden); - preconditions.checkState(_credentials && _credentials.email); - $rootScope.starting = true; - - $timeout(function() { - pinService.save(pin, _credentials.email, _credentials.password, function(err) { - _credentials.password = ''; - _credentials = null; - $scope.askForPin = 0; - $rootScope.hasPin = true; - $rootScope.starting = null; - $scope.openWallets(); - }); - }, 100); - }; - - $scope.openWithCredentials = function(form) { - if (form && form.$invalid) { - $scope.error = 'Please enter the required fields'; - return; - } - - $timeout(function() { - $scope.open(form.email.$modelValue, form.password.$modelValue); - }, 100); - }; - - - $scope.pinLogout = function() { - pinService.clear(function() { - copay.logger.debug('PIN erased'); - delete $rootScope['hasPin']; - applicationService.restart(); - }); - }; - - $scope.open = function(email, password) { - $rootScope.starting = true; - identityService.open(email, password, function(err, iden) { - if (err) { - $rootScope.starting = false; - copay.logger.warn(err); - - var identifier = $scope.usingLocalStorage ? 'username' : 'email'; - if ((err.toString() || '').match('PNOTFOUND')) { - $scope.error = 'Invalid ' + identifier + ' or password'; - - if ($scope.attempt++ > 1) { - var storage = $scope.usingLocalStorage ? 'this device storage' : 'cloud storage'; - $scope.error = 'Invalid ' + identifier + ' or password. You are trying to sign in using ' + storage + '. Change it on settings if necessary.'; - }; - - $rootScope.hasPin = false; - pinService.clear(function() {}); - - } else if ((err.toString() || '').match('Connection')) { - $scope.error = 'Could not connect to Insight Server'; - } else if ((err.toString() || '').match('Unable')) { - $scope.error = 'Unable to read data from the Insight Server'; - } else { - $scope.error = 'Unknown error'; - } - $timeout(function() { - $rootScope.$digest(); - }, 1) - return; - } - - // Open successfully? - if (iden) { - $scope.error = null; - $scope.confirmedEmail = false; - - // mobile - if ($scope.isMobile && !$rootScope.hasPin) { - _credentials = { - email: email, - password: password, - }; - $scope.askForPin = 1; - $rootScope.starting = false; - $rootScope.hideNavigation = true; - $timeout(function() { - $rootScope.$digest(); - }, 1); - return; - } - // no mobile - else { - $scope.openWallets(); - } - } - }); - }; - - function getParam(sname) { - var params = location.search.substr(location.search.indexOf("?") + 1); - var sval = ""; - params = params.split("&"); - // split param and value into individual pieces - for (var i = 0; i < params.length; i++) { - var temp = params[i].split("="); - if ([temp[0]] == sname) { - sval = temp[1]; - } - } - return sval; - } -}); diff --git a/js/controllers/homeWallet.js b/js/controllers/homeWallet.js deleted file mode 100644 index 9b5df970f..000000000 --- a/js/controllers/homeWallet.js +++ /dev/null @@ -1,103 +0,0 @@ -'use strict'; - -angular.module('copayApp.controllers').controller('HomeWalletController', function($scope, $rootScope, $timeout, $filter, $modal, rateService, notification, txStatus, identityService, isCordova) { - - $scope.openTxModal = function(tx) { - var ModalInstanceCtrl = function($scope, $modalInstance) { - var w = $rootScope.wallet; - $scope.error = null; - $scope.tx = tx; - $scope.registeredCopayerIds = w.getRegisteredCopayerIds(); - $scope.loading = null; - - $scope.getShortNetworkName = function() { - var w = $rootScope.wallet; - return w.getNetworkName().substring(0, 4); - }; - - $scope.sign = function(ntxid) { - if (isCordova) { - window.plugins.spinnerDialog.show(null, 'Signing transaction...', true); - } - $scope.loading = true; - $scope.error = null; - $timeout(function() { - w.signAndSend(ntxid, function(err, id, status) { - if (isCordova) { - window.plugins.spinnerDialog.hide(); - } - $scope.loading = false; - if (err) { - $scope.error = 'Transaction could not send. Please try again.'; - $scope.$digest(); - } - else { - $modalInstance.close(status); - } - }); - }, 100); - }; - - $scope.reject = function(ntxid) { - if (isCordova) { - window.plugins.spinnerDialog.show(null, 'Rejecting transaction...', true); - } - $scope.loading = true; - $scope.error = null; - $timeout(function() { - w.reject(ntxid, function(err, status) { - if (isCordova) { - window.plugins.spinnerDialog.hide(); - } - $scope.loading = false; - if (err) { - $scope.error = err; - $scope.$digest(); - } - else { - $modalInstance.close(status); - } - }); - }, 100); - }; - - $scope.broadcast = function(ntxid) { - if (isCordova) { - window.plugins.spinnerDialog.show(null, 'Sending transaction...', true); - } - $scope.loading = true; - $scope.error = null; - $timeout(function() { - w.issueTx(ntxid, function(err, txid, status) { - if (isCordova) { - window.plugins.spinnerDialog.hide(); - } - $scope.loading = false; - if (err) { - $scope.error = 'Transaction could not send. Please try again.'; - $scope.$digest(); - } - else { - $modalInstance.close(status); - } - }); - }, 100); - }; - - $scope.cancel = function() { - $modalInstance.dismiss('cancel'); - }; - }; - - var modalInstance = $modal.open({ - templateUrl: 'views/modals/txp-details.html', - windowClass: 'medium', - controller: ModalInstanceCtrl, - }); - - modalInstance.result.then(function(status) { - txStatus.notify(status); - }); - - }; -}); diff --git a/js/controllers/import.js b/js/controllers/import.js deleted file mode 100644 index 933d7f16c..000000000 --- a/js/controllers/import.js +++ /dev/null @@ -1,110 +0,0 @@ -'use strict'; - -angular.module('copayApp.controllers').controller('ImportController', - function($scope, $rootScope, $location, $timeout, identityService, notification, isMobile, isCordova, Compatibility) { - - $rootScope.title = 'Import wallet'; - $scope.importStatus = 'Importing wallet - Reading backup...'; - $scope.hideAdv = true; - $scope.isSafari = isMobile.Safari(); - $scope.isCordova = isCordova; - $scope.importOpts = {}; - $rootScope.hideWalletNavigation = true; - - - window.ignoreMobilePause = true; - $scope.$on('$destroy', function() { - $timeout(function(){ - window.ignoreMobilePause = false; - }, 100); - }); - - Compatibility.check($scope); - - var reader = new FileReader(); - - var updateStatus = function(status) { - $scope.importStatus = status; - } - - - $scope.getFile = function() { - // If we use onloadend, we need to check the readyState. - reader.onloadend = function(evt) { - if (evt.target.readyState == FileReader.DONE) { // DONE == 2 - var encryptedObj = evt.target.result; - updateStatus('Importing wallet - Procesing backup...'); - identityService.importWallet(encryptedObj, $scope.password, {}, function(err) { - if (err) { - $rootScope.starting = false; - $scope.error = 'Could not read wallet. Please check your password'; - $timeout(function() { - $rootScope.$digest(); - }, 1); - } - }); - } - } - }; - - $scope.import = function(form) { - - if (form.$invalid) { - $scope.error = 'There is an error in the form'; - return; - } - - var backupFile = $scope.file; - var backupText = form.backupText.$modelValue; - var backupOldWallet = form.backupOldWallet.$modelValue; - var password = form.password.$modelValue; - - if (backupOldWallet) { - backupText = backupOldWallet.value; - } - - if (!backupFile && !backupText) { - $scope.error = 'Please, select your backup file'; - return; - } - - $rootScope.starting = true; - - $timeout(function() { - - $scope.importOpts = {}; - - var skipFields = []; - - if ($scope.skipPublicKeyRing) - skipFields.push('publicKeyRing'); - - if ($scope.skipTxProposals) - skipFields.push('txProposals'); - - if (skipFields) - $scope.importOpts.skipFields = skipFields; - - if (backupFile) { - reader.readAsBinaryString(backupFile); - } else { - updateStatus('Importing wallet - Procesing backup...'); - identityService.importWallet(backupText, $scope.password, $scope.importOpts, function(err) { - if (err) { - $rootScope.starting = false; - $scope.error = 'Could not read wallet. Please check your password'; - $timeout(function() { - $rootScope.$digest(); - }, 1); - } - }); - } - }, 100); - }; - - - $scope.$on("$destroy", function () { - $rootScope.hideWalletNavigation = false; - }); - - }); diff --git a/js/controllers/index.js b/js/controllers/index.js deleted file mode 100644 index 3f4e85eac..000000000 --- a/js/controllers/index.js +++ /dev/null @@ -1,34 +0,0 @@ -'use strict'; - -angular.module('copayApp.controllers').controller('IndexController', function($scope, $timeout, go, isCordova, identityService, notification) { - $scope.init = function() { - - }; - - $scope.resendVerificationEmail = function() { - $scope.loading = true; - identityService.resendVerificationEmail(function(err) { - if (err) { - notification.error('Could not send email', 'There was a problem sending the verification email.'); - } - else { - notification.success('Email sent', 'Check your inbox and confirms the email'); - $scope.hideReSendButton = true; - } - $scope.loading = null; - $timeout(function() { - $scope.$digest(); - }, 1); - return; - }); - }; - - $scope.openMenu = function() { - go.swipe(true); - }; - - $scope.closeMenu = function() { - go.swipe(); - }; - -}); diff --git a/js/controllers/join.js b/js/controllers/join.js deleted file mode 100644 index 3234e1bae..000000000 --- a/js/controllers/join.js +++ /dev/null @@ -1,160 +0,0 @@ -'use strict'; - -angular.module('copayApp.controllers').controller('JoinController', - function($scope, $rootScope, $timeout, isMobile, notification, identityService) { - $rootScope.fromSetup = false; - $scope.loading = false; - $scope.isMobile = isMobile.any(); - $rootScope.title = 'Join shared wallet'; - $rootScope.hideWalletNavigation = true; - - - // QR code Scanner - var cameraInput; - var video; - var canvas; - var $video; - var context; - var localMediaStream; - - $scope.hideAdv = true; - - - navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia; - - if (!window.cordova && !navigator.getUserMedia) - $scope.disableScanner = 1; - - 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.joinForm.connectionId.$setViewValue(data); - $scope.joinForm.connectionId.$render(); - }); - }; - - $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() { - window.ignoreMobilePause = true; - cordova.plugins.barcodeScanner.scan( - function onSuccess(result) { - $timeout(function(){ - window.ignoreMobilePause = false; - }, 100); - if (result.cancelled) return; - - $scope.connectionId = result.text; - $rootScope.$digest(); - }, - function onError(error) { - $timeout(function(){ - window.ignoreMobilePause = false; - }, 100); - alert('Scanning error'); - }); - } - - - $scope.join = function(form) { - if (form && form.$invalid) { - notification.error('Error', 'Please enter the required fields'); - return; - } - - $rootScope.starting = true; - identityService.joinWallet({ - secret: $scope.connectionId, - nickname: $scope.nickname, - privateHex: $scope.private, - }, function(err) { - $rootScope.starting = false; - if (err) { - if (err === 'joinError') - notification.error('Fatal error connecting to Insight server'); - else if (err === 'walletFull') - notification.error('The wallet is full'); - else if (err === 'walletAlreadyExists') - notification.error('Wallet already exists', 'Cannot join again from the same profile'); - else if (err === 'badNetwork') - notification.error('Network Error', 'Wallet network configuration missmatch'); - else if (err === 'badSecret') - notification.error('Bad secret', 'The secret string you entered is invalid'); - else { - notification.error('Error', err.message || err); - } - } - $timeout(function () { $scope.$digest(); }, 1); - }); - } - - - $scope.$on("$destroy", function () { - $rootScope.hideWalletNavigation = false; - }); - }); diff --git a/js/controllers/more.js b/js/controllers/more.js deleted file mode 100644 index c7d489889..000000000 --- a/js/controllers/more.js +++ /dev/null @@ -1,183 +0,0 @@ -'use strict'; - -angular.module('copayApp.controllers').controller('MoreController', - function($scope, $rootScope, $location, $filter, $timeout, balanceService, notification, rateService, backupService, identityService, isMobile, isCordova, go, pendingTxsService) { - var w = $rootScope.wallet; - var max = $rootScope.quotaPerItem; - $scope.isSafari = isMobile.Safari(); - $scope.isCordova = isCordova; - $scope.wallet = w; - $scope.error = null; - $scope.success = null; - - var bits = w.sizes().total; - w.kb = $filter('noFractionNumber')(bits / 1000, 1); - if (max) { - w.usage = $filter('noFractionNumber')(bits / max * 100, 0); - } - - $rootScope.title = 'Settings'; - - $scope.unitOpts = [{ - name: 'Satoshis (100,000,000 satoshis = 1BTC)', - shortName: 'SAT', - value: 1, - decimals: 0 - }, { - name: 'bits (1,000,000 bits = 1BTC)', - shortName: 'bits', - value: 100, - decimals: 2 - }, { - name: 'mBTC (1,000 mBTC = 1BTC)', - shortName: 'mBTC', - value: 100000, - decimals: 5 - }, { - name: 'BTC', - shortName: 'BTC', - value: 100000000, - decimals: 8 - }]; - - $scope.selectedAlternative = { - name: w.settings.alternativeName, - isoCode: w.settings.alternativeIsoCode - }; - $scope.alternativeOpts = rateService.isAvailable() ? - rateService.listAlternatives() : [$scope.selectedAlternative]; - - rateService.whenAvailable(function() { - $scope.alternativeOpts = rateService.listAlternatives(); - for (var ii in $scope.alternativeOpts) { - if (w.settings.alternativeIsoCode === $scope.alternativeOpts[ii].isoCode) { - $scope.selectedAlternative = $scope.alternativeOpts[ii]; - } - } - }); - - - for (var ii in $scope.unitOpts) { - if (w.settings.unitName === $scope.unitOpts[ii].shortName) { - $scope.selectedUnit = $scope.unitOpts[ii]; - break; - } - } - - $scope.hideAdv = true; - $scope.hidePriv = true; - $scope.hideSecret = true; - if (w) { - $scope.priv = w.privateKey.toObj().extendedPrivateKeyString; - $scope.secret = w.getSecret(); - } - - setTimeout(function() { - $scope.$digest(); - }, 1); - - $scope.save = function() { - var w = $rootScope.wallet; - w.changeSettings({ - unitName: $scope.selectedUnit.shortName, - unitToSatoshi: $scope.selectedUnit.value, - unitDecimals: $scope.selectedUnit.decimals, - alternativeName: $scope.selectedAlternative.name, - alternativeIsoCode: $scope.selectedAlternative.isoCode, - }); - notification.success('Success', $filter('translate')('settings successfully updated')); - balanceService.update(w, function() { - pendingTxsService.update(); - $rootScope.$digest(); - }); - }; - - $scope.purge = function(deleteAll) { - var removed = w.purgeTxProposals(deleteAll); - if (removed) { - balanceService.update(w, function() { - $rootScope.$digest(); - }, true); - } - notification.info('Transactions Proposals Purged', removed + ' ' + $filter('translate')('transaction proposal purged')); - }; - - $scope.updateIndexes = function() { - var w = $rootScope.wallet; - notification.info('Scaning for transactions', 'Using derived addresses from your wallet'); - w.updateIndexes(function(err) { - notification.info('Scan Ended', 'Updating balance'); - if (err) { - notification.error('Error', $filter('translate')('Error updating indexes: ') + err); - } - balanceService.update(w, function() { - notification.info('Finished', 'The balance is updated using the derived addresses'); - w.sendIndexes(); - $rootScope.$digest(); - }, true); - }); - }; - - $scope.deleteWallet = function() { - $scope.loading = true; - $timeout(function() { - identityService.deleteWallet(w, function(err) { - $scope.loading = false; - if (err) { - $scope.error = err.message || err; - copay.logger.warn(err); - $timeout(function () { $scope.$digest(); }); - } else { - if ($rootScope.wallet) { - go.walletHome(); - } - $timeout(function() { - notification.success('Success', 'The wallet "' + (w.name || w.id) + '" was deleted'); - }); - } - }); - }, 100); - }; - - $scope.copyText = function(text) { - if (isCordova) { - window.cordova.plugins.clipboard.copy(text); - window.plugins.toast.showShortCenter('Copied to clipboard'); - } - }; - - $scope.downloadWalletBackup = function() { - backupService.walletDownload(w); - }; - - $scope.viewWalletBackup = function() { - $scope.loading = true; - $timeout(function() { - $scope.backupWalletPlainText = backupService.walletEncrypted(w); - }, 100); - }; - - $scope.copyWalletBackup = function() { - var ew = backupService.walletEncrypted(w); - window.cordova.plugins.clipboard.copy(ew); - window.plugins.toast.showShortCenter('Copied to clipboard'); - }; - - $scope.sendWalletBackup = function() { - if (isMobile.Android() || isMobile.Windows()) { - window.ignoreMobilePause = true; - } - window.plugins.toast.showShortCenter('Preparing backup...'); - var name = (w.name || w.id); - var ew = backupService.walletEncrypted(w); - var properties = { - subject: 'Copay Wallet Backup: ' + name, - body: 'Here is the encrypted backup of the wallet ' - + name + ': \n\n' + ew - + '\n\n To import this backup, copy all text between {...}, including the symbols {}', - isHtml: false - }; - window.plugin.email.open(properties); - }; - - }); diff --git a/js/controllers/paymentUri.js b/js/controllers/paymentUri.js deleted file mode 100644 index dd855cc8c..000000000 --- a/js/controllers/paymentUri.js +++ /dev/null @@ -1,20 +0,0 @@ -var bitcore = require('bitcore'); - -angular.module('copayApp.controllers').controller('paymentUriController', function($rootScope, $scope, $routeParams, $location, go) { - - // Build bitcoinURI with querystring - var query = []; - angular.forEach($location.search(), function(value, key) { - query.push(key + "=" + value); - }); - var queryString = query ? query.join("&") : null; - var bitcoinURI = $routeParams.data + ( queryString ? '?' + queryString : ''); - var uri = new bitcore.BIP21(bitcoinURI); - - if (uri && uri.address && (_.isString(uri.address) || uri.address.isValid()) ) { - copay.logger.debug('Payment Intent:', bitcoinURI); - $rootScope.pendingPayment = bitcoinURI; - } - - go.home(); -}); diff --git a/js/controllers/receive.js b/js/controllers/receive.js deleted file mode 100644 index cdaa0b868..000000000 --- a/js/controllers/receive.js +++ /dev/null @@ -1,106 +0,0 @@ -'use strict'; - -angular.module('copayApp.controllers').controller('ReceiveController', - function($scope, $rootScope, $timeout, $modal, isCordova, isMobile) { - - $scope.newAddr = function() { - var w = $rootScope.wallet; - var lastAddr = w.generateAddress(null); - $scope.setAddressList(); - $scope.addr = lastAddr; - }; - - $scope.copyAddress = function(addr) { - if (isCordova) { - window.cordova.plugins.clipboard.copy('bitcoin:' + addr); - window.plugins.toast.showShortCenter('Copied to clipboard'); - } - }; - - $scope.shareAddress = function(addr) { - if (isCordova) { - if (isMobile.Android() || isMobile.Windows()) { - window.ignoreMobilePause = true; - } - window.plugins.socialsharing.share('bitcoin:' + addr, null, null, null); - } - }; - - $scope.init = function() { - $rootScope.title = 'Receive'; - $scope.showAll = false; - $scope.isCordova = isCordova; - - var w = $rootScope.wallet; - var lastAddr = _.first(w.getAddressesOrdered()); - var balance = w.balanceInfo.balanceByAddr; - $scope.setAddressList(); - - while (balance && balance[lastAddr] > 0) { - $scope.loading = true; - lastAddr = w.generateAddress(null); - }; - $scope.loading = false; - $scope.addr = lastAddr; - }; - - $scope.openAddressModal = function(address) { - var scope = $scope; - var ModalInstanceCtrl = function($scope, $modalInstance, address) { - $scope.address = address; - $scope.isCordova = isCordova; - $scope.copyAddress = function(addr) { - scope.copyAddress(addr); - }; - - $scope.cancel = function() { - $modalInstance.dismiss('cancel'); - }; - }; - - $modal.open({ - templateUrl: 'views/modals/qr-address.html', - windowClass: 'small', - controller: ModalInstanceCtrl, - resolve: { - address: function() { - return address; - } - } - }); - }; - - $scope.toggleShowAll = function() { - $scope.showAll = !$scope.showAll; - $scope.setAddressList(); - }; - - $scope.setAddressList = function() { - if ($scope.showAll) { - var w = $rootScope.wallet; - var balance = w.balanceInfo.balanceByAddr; - - var addresses = w.getAddressesOrdered(); - if (addresses) { - $scope.addrLength = addresses.length; - - if (!$scope.showAll) - addresses = addresses.slice(0, 3); - - var list = []; - _.each(addresses, function(address, index) { - list.push({ - 'index': index, - 'address': address, - 'balance': balance ? balance[address] : null, - 'isChange': w.addressIsChange(address), - }); - }); - $scope.addresses = list; - } - } else { - $scope.addresses = []; - } - }; - } -); diff --git a/js/controllers/send.js b/js/controllers/send.js deleted file mode 100644 index 2c7a372d8..000000000 --- a/js/controllers/send.js +++ /dev/null @@ -1,602 +0,0 @@ -'use strict'; -var bitcore = require('bitcore'); -var preconditions = require('preconditions').singleton(); - -angular.module('copayApp.controllers').controller('SendController', - function($scope, $rootScope, $window, $timeout, $modal, $filter, notification, isMobile, rateService, txStatus, isCordova) { - - $scope.init = function() { - var w = $rootScope.wallet; - preconditions.checkState(w); - - preconditions.checkState(w.settings.unitToSatoshi); - - $scope.isMobile = isMobile.any(); - $scope.isWindowsPhoneApp = isMobile.Windows() && isCordova; - $rootScope.wpInputFocused = false; - - $scope.isShared = w.isShared(); - $scope.requiresMultipleSignatures = w.requiresMultipleSignatures(); - $rootScope.title = $scope.requiresMultipleSignatures ? 'Send Proposal' : 'Send'; - $scope.loading = false; - $scope.error = $scope.success = null; - - $scope.alternativeName = w.settings.alternativeName; - $scope.alternativeIsoCode = w.settings.alternativeIsoCode; - - $scope.isRateAvailable = false; - $scope.rateService = rateService; - $scope.showScanner = false; - $scope.myId = w.getMyCopayerId(); - $scope.isMobile = isMobile.any(); - - if ($rootScope.pendingPayment) { - $timeout(function() { - $scope.setFromUri($rootScope.pendingPayment) - $rootScope.pendingPayment = null; - }, 100); - } - - $scope.setInputs(); - $scope.setScanner(); - - rateService.whenAvailable(function() { - $scope.isRateAvailable = true; - $scope.$digest(); - }); - }; - - if (isCordova) { - var openScannerCordova = $rootScope.$on('dataScanned', function(event, data) { - $scope.sendForm.address.$setViewValue(data); - $scope.sendForm.address.$render(); - }); - - $scope.$on('$destroy', function() { - openScannerCordova(); - }); - } - - $scope.formFocus = function(what) { - if (!$scope.isWindowsPhoneApp) return - - if (!what) { - $rootScope.wpInputFocused = false; - $scope.hideAddress = false; - $scope.hideAmount = false; - - } else { - $rootScope.wpInputFocused = true; - if (what == 'amount') { - $scope.hideAddress = true; - } else if (what == 'msg') { - $scope.hideAddress = true; - $scope.hideAmount = true; - } - - } - $timeout(function() { - $rootScope.$digest(); - }, 1); - }; - - $scope.setInputs = function() { - var w = $rootScope.wallet; - var unitToSat = w.settings.unitToSatoshi; - var satToUnit = 1 / unitToSat; - /** - * Setting the two related amounts as properties prevents an infinite - * recursion for watches while preserving the original angular updates - * - */ - Object.defineProperty($scope, - "_alternative", { - get: function() { - return this.__alternative; - }, - set: function(newValue) { - this.__alternative = newValue; - if (typeof(newValue) === 'number' && $scope.isRateAvailable) { - this._amount = parseFloat( - (rateService.fromFiat(newValue, $scope.alternativeIsoCode) * satToUnit).toFixed(w.settings.unitDecimals), 10); - } else { - this._amount = 0; - } - }, - enumerable: true, - configurable: true - }); - Object.defineProperty($scope, - "_amount", { - get: function() { - return this.__amount; - }, - set: function(newValue) { - this.__amount = newValue; - if (typeof(newValue) === 'number' && $scope.isRateAvailable) { - this.__alternative = parseFloat( - (rateService.toFiat(newValue * unitToSat, $scope.alternativeIsoCode)).toFixed(2), 10); - } else { - this.__alternative = 0; - } - }, - enumerable: true, - configurable: true - }); - - Object.defineProperty($scope, - "_address", { - get: function() { - return this.__address; - }, - set: function(newValue) { - this.__address = $scope.onAddressChange(newValue); - }, - enumerable: true, - configurable: true - }); - }; - - $scope.setScanner = function() { - navigator.getUserMedia = navigator.getUserMedia || - navigator.webkitGetUserMedia || navigator.mozGetUserMedia || - navigator.msGetUserMedia; - window.URL = window.URL || window.webkitURL || - window.mozURL || window.msURL; - - if (!window.cordova && !navigator.getUserMedia) - $scope.disableScanner = 1; - }; - - - $scope.setError = function(err) { - var w = $rootScope.wallet; - copay.logger.warn(err); - - var msg = err.toString(); - if (msg.match('BIG')) - msg = 'The transaction have too many inputs. Try creating many transactions for smaller amounts' - - if (msg.match('totalNeededAmount') || msg.match('unspent not set')) - msg = 'Insufficient funds' - - if (msg.match('expired')) - msg = 'The payment request has expired'; - - if (msg.match('XMLHttpRequest')) - msg = 'Error when sending to the blockchain. Resend it from Home'; - - var message = 'The transaction' + ($scope.requiresMultipleSignatures ? ' proposal' : '') + - ' could not be created: ' + msg; - - $scope.error = message; - - $timeout(function() { - $scope.$digest(); - }, 1); - }; - - $scope.submitForm = function(form) { - var w = $rootScope.wallet; - var unitToSat = w.settings.unitToSatoshi; - - if (form.$invalid) { - $scope.error = 'Unable to send transaction proposal'; - return; - } - - if (isCordova) { - window.plugins.spinnerDialog.show(null, 'Creating transaction...', true); - } - - $scope.loading = true; - if ($scope.isWindowsPhoneApp) - $rootScope.wpInputFocused = true; - - $timeout(function () { - var comment = form.comment.$modelValue; - var merchantData = $scope._merchantData; - var address, amount; - if (!merchantData) { - address = form.address.$modelValue; - amount = parseInt((form.amount.$modelValue * unitToSat).toFixed(0)); - } - - w.spend({ - merchantData: merchantData, - toAddress: address, - amountSat: amount, - comment: comment, - }, function (err, txid, status) { - if (isCordova) { - window.plugins.spinnerDialog.hide(); - } - $scope.loading = false; - if ($scope.isWindowsPhoneApp) - $rootScope.wpInputFocused = false; - - if (err) { - $scope.setError(err); - } - else { - txStatus.notify(status); - $scope.resetForm(); - } - }); - }, 100); - }; - - // QR code Scanner - var cameraInput; - var video; - var canvas; - var $video; - var context; - var localMediaStream; - - var _scan = function(evt) { - if ($scope.isMobile) { - $scope.scannerLoading = true; - var files = evt.target.files; - - if (files.length === 1 && files[0].type.indexOf('image/') === 0) { - var file = files[0]; - - var reader = new FileReader(); - reader.onload = (function(theFile) { - return function(e) { - var mpImg = new MegaPixImage(file); - mpImg.render(canvas, { - maxWidth: 200, - maxHeight: 200, - orientation: 6 - }); - - $timeout(function() { - qrcode.width = canvas.width; - qrcode.height = canvas.height; - qrcode.imagedata = context.getImageData(0, 0, qrcode.width, qrcode.height); - - try { - qrcode.decode(); - } catch (e) { - // error decoding QR - } - }, 1500); - }; - })(file); - - // Read in the file as a data URL - reader.readAsDataURL(file); - } - } else { - 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.scannerLoading = false; - $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.sendForm.address.$setViewValue(data); - $scope.sendForm.address.$render(); - }); - }; - - $scope.cancelScanner = function() { - _scanStop(); - }; - - $scope.openScanner = function() { - $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.setTopAmount = function() { - var w = $rootScope.wallet; - var form = $scope.sendForm; - if (form) { - form.amount.$setViewValue(w.balanceInfo.topAmount); - form.amount.$render(); - form.amount.$isValid = true; - } - }; - - $scope.setForm = function(to, amount, comment) { - var form = $scope.sendForm; - if (to) { - form.address.$setViewValue(to); - form.address.$isValid = true; - form.address.$render(); - $scope.lockAddress = true; - } - - if (amount) { - form.amount.$setViewValue("" + amount); - form.amount.$isValid = true; - form.amount.$render(); - $scope.lockAmount = true; - } - - if (comment) { - form.comment.$setViewValue(comment); - form.comment.$isValid = true; - form.comment.$render(); - } - }; - - $scope.resetForm = function() { - var form = $scope.sendForm; - - $scope.fetchingURL = null; - $scope._merchantData = $scope._domain = null; - - $scope.lockAddress = false; - $scope.lockAmount = false; - - $scope._amount = $scope._address = null; - - form.amount.$pristine = true; - form.amount.$setViewValue(''); - form.amount.$render(); - - form.comment.$setViewValue(''); - form.comment.$render(); - form.$setPristine(); - - if (form.address) { - form.address.$pristine = true; - form.address.$setViewValue(''); - form.address.$render(); - } - $timeout(function() { - $rootScope.$digest(); - }, 1); - }; - - var $oscope = $scope; - $scope.openPPModal = function(merchantData) { - var ModalInstanceCtrl = function($scope, $modalInstance) { - var w = $rootScope.wallet; - var satToUnit = 1 / w.settings.unitToSatoshi; - $scope.md = merchantData; - $scope.alternative = $oscope._alternative; - $scope.alternativeIsoCode = $oscope.alternativeIsoCode; - $scope.isRateAvailable = $oscope.isRateAvailable; - $scope.unitTotal = (merchantData.total * satToUnit).toFixed(w.settings.unitDecimals); - - $scope.cancel = function() { - $modalInstance.dismiss('cancel'); - }; - }; - $modal.open({ - templateUrl: 'views/modals/paypro.html', - windowClass: 'medium', - controller: ModalInstanceCtrl, - }); - }; - - - $scope.setFromPayPro = function(uri) { - - var isChromeApp = window.chrome && chrome.runtime && chrome.runtime.id; - if (isChromeApp) { - $scope.error = 'Payment Protocol not yet supported on ChromeApp'; - return; - } - - var w = $rootScope.wallet; - var satToUnit = 1 / w.settings.unitToSatoshi; - $scope.fetchingURL = uri; - $scope.loading = true; - - - // Payment Protocol URI (BIP-72) - w.fetchPaymentRequest({ - url: uri - }, function(err, merchantData) { - $scope.loading = false; - $scope.fetchingURL = null; - - if (err) { - copay.logger.warn(err); - $scope.resetForm(); - var msg = err.toString(); - if (msg.match('HTTP')) { - msg = 'Could not fetch payment information'; - } - $scope.error = msg; - } else { - $scope._merchantData = merchantData; - $scope._domain = merchantData.domain; - $scope.setForm(null, (merchantData.total * satToUnit).toFixed(w.settings.unitDecimals)); - } - }); - }; - - $scope.setFromUri = function(uri) { - function sanitizeUri(uri) { - // Fixes when a region uses comma to separate decimals - var regex = /[\?\&]amount=(\d+([\,\.]\d+)?)/i; - var match = regex.exec(uri); - if (!match || match.length === 0) { - return uri; - } - var value = match[0].replace(',', '.'); - var newUri = uri.replace(regex, value); - return newUri; - }; - - var w = $rootScope.wallet; - var satToUnit = 1 / w.settings.unitToSatoshi; - var form = $scope.sendForm; - - uri = sanitizeUri(uri); - - var parsed = new bitcore.BIP21(uri); - if (!parsed.isValid() || !parsed.address.isValid()) { - $scope.error = 'Invalid bitcoin URL'; - form.address.$isValid = false; - return uri; - }; - - var addr = parsed.address.toString(); - if (parsed.data.merchant) - return $scope.setFromPayPro(parsed.data.merchant); - - var amount = (parsed.data && parsed.data.amount) ? - ((parsed.data.amount * 100000000).toFixed(0) * satToUnit).toFixed(w.settings.unitDecimals) : 0; - - $scope.setForm(addr, amount, parsed.data.message, true); - return addr; - }; - - $scope.onAddressChange = function(value) { - $scope.error = $scope.success = null; - if (!value) return ''; - - if (value.indexOf('bitcoin:') === 0) { - return $scope.setFromUri(value); - } else if (/^https?:\/\//.test(value)) { - return $scope.setFromPayPro(value); - } - - return value; - }; - - $scope.openAddressBook = function() { - var w = $rootScope.wallet; - var modalInstance = $modal.open({ - templateUrl: 'views/modals/address-book.html', - windowClass: 'large', - controller: function($scope, $modalInstance) { - - $scope.showForm = null; - $scope.addressBook = w.addressBook; - - $scope.hasEntry = function() { - return _.keys($scope.addressBook).length > 0 ? true : false; - }; - - $scope.toggleAddressBookEntry = function(key) { - w.toggleAddressBookEntry(key); - }; - - $scope.copyToSend = function(addr) { - $modalInstance.close(addr); - }; - - $scope.cancel = function(form) { - $scope.error = $scope.success = $scope.newaddress = $scope.newlabel = null; - clearForm(form); - $scope.toggleForm(); - }; - - $scope.toggleForm = function() { - $scope.showForm = !$scope.showForm; - }; - - var clearForm = function(form) { - form.newaddress.$pristine = true; - form.newaddress.$setViewValue(''); - form.newaddress.$render(); - - form.newlabel.$pristine = true; - form.newlabel.$setViewValue(''); - form.newlabel.$render(); - form.$setPristine(); - }; - - // TODO change to modal - $scope.submitAddressBook = function(form) { - if (form.$invalid) { - return; - } - $scope.loading = true; - $timeout(function() { - var errorMsg; - var entry = { - "address": form.newaddress.$modelValue, - "label": form.newlabel.$modelValue - }; - try { - w.setAddressBook(entry.address, entry.label); - } catch (e) { - copay.logger.warn(e); - errorMsg = e.message; - } - - if (errorMsg) { - $scope.error = errorMsg; - } else { - clearForm(form); - $scope.toggleForm(); - notification.success('Entry created', 'New addressbook entry created') - } - $scope.loading = false; - $rootScope.$digest(); - }, 100); - return; - }; - - $scope.close = function() { - $modalInstance.dismiss('cancel'); - }; - }, - }); - - modalInstance.result.then(function(addr) { - $scope.setForm(addr); - }); - }; - }); diff --git a/js/controllers/settings.js b/js/controllers/settings.js deleted file mode 100644 index d04a778cb..000000000 --- a/js/controllers/settings.js +++ /dev/null @@ -1,86 +0,0 @@ -'use strict'; - -angular.module('copayApp.controllers').controller('SettingsController', function($scope, $rootScope, $window, $route, $location, notification, configService) { - $scope.title = 'Settings'; - $scope.insightLivenet = config.network.livenet.url; - $scope.insightTestnet = config.network.testnet.url; - $scope.defaultLogLevel = config.logLevel || 'log'; - - var logLevels = copay.logger.getLevels(); - - $scope.availableLogLevels = []; - - for (var key in logLevels) { - $scope.availableLogLevels.push({ - 'name': key - }); - } - - $scope.availableStorages = [{ - name: 'In the cloud (Insight server)', - pluginName: 'EncryptedInsightStorage', - }, { - name: 'On this device (localstorage)', - pluginName: 'EncryptedLocalStorage', - }, - // { - // name: 'GoogleDrive', - // pluginName: 'GoogleDrive', - // } - ]; - - _.each($scope.availableStorages, function(v) { - if (config.plugins[v.pluginName]) - $scope.selectedStorage = v; - }); - - for (var ii in $scope.availableLogLevels) { - if ($scope.defaultLogLevel === $scope.availableLogLevels[ii].name) { - $scope.selectedLogLevel = $scope.availableLogLevels[ii]; - break; - } - } - - $scope.save = function() { - $scope.insightLivenet = copay.Insight.setCompleteUrl($scope.insightLivenet); - $scope.insightTestnet = copay.Insight.setCompleteUrl($scope.insightTestnet); - - var insightSettings = { - livenet: { - url: $scope.insightLivenet, - transports: ['polling'], - }, - testnet: { - url: $scope.insightTestnet, - transports: ['polling'], - }, - } - - var plugins = {}; - plugins[$scope.selectedStorage.pluginName] = true; - - configService.set({ - network: insightSettings, - plugins: plugins, - logLevel: $scope.selectedLogLevel.name, - EncryptedInsightStorage: _.extend(config.EncryptedInsightStorage, { - url: insightSettings.livenet.url + '/api/email' - }), - rates: _.extend(config.rates, { - url: insightSettings.livenet.url + '/api/rates' - }), - }, - function() { - notification.success('Settings saved',"Settings were saved"); - $location.path('/'); - }); - }; - - $scope.reset = function() { - configService.reset(function() { - notification.success('Settings reseted',"Settings were reseted"); - $location.path('/'); - }); - }; - -}); diff --git a/js/controllers/sidebar.js b/js/controllers/sidebar.js deleted file mode 100644 index 8904f7a55..000000000 --- a/js/controllers/sidebar.js +++ /dev/null @@ -1,121 +0,0 @@ -'use strict'; - -angular.module('copayApp.controllers').controller('SidebarController', function($scope, $rootScope, $location, $timeout, identityService, isMobile, isCordova, go) { - - $scope.isMobile = isMobile.any(); - $scope.isCordova = isCordova; - $scope.username = $rootScope.iden ? $rootScope.iden.getName() : ''; - - $scope.menu = [{ - 'title': 'Home', - 'icon': 'icon-home', - 'link': 'homeWallet' - }, { - 'title': 'Receive', - 'icon': 'icon-receive', - 'link': 'receive' - }, { - 'title': 'Send', - 'icon': 'icon-paperplane', - 'link': 'send' - }, { - 'title': 'History', - 'icon': 'icon-history', - 'link': 'history' - }]; - - $scope.signout = function() { - identityService.signout(); - }; - - $scope.isActive = function(item) { - return item.link && item.link == $location.path().split('/')[1]; - }; - - $scope.switchWallet = function(wid) { - $scope.walletSelection = false; - identityService.setFocusedWallet(wid); - go.walletHome(); - }; - - $scope.toggleWalletSelection = function() { - $scope.walletSelection = !$scope.walletSelection; - if (!$scope.walletSelection) return; - $scope.setWallets(); - }; - - $scope.openScanner = function() { - window.ignoreMobilePause = true; - cordova.plugins.barcodeScanner.scan( - function onSuccess(result) { - $timeout(function() { - window.ignoreMobilePause = false; - }, 100); - if (result.cancelled) return; - - $timeout(function() { - var data = result.text; - $scope.$apply(function() { - $rootScope.$emit('dataScanned', data); - }); - }, 1000); - }, - function onError(error) { - $timeout(function() { - window.ignoreMobilePause = false; - }, 100); - alert('Scanning error'); - } - ); - go.send(); - }; - - $scope.init = function() { - // This should be called only once. - - // focused wallet change - if ($rootScope.wallet) { - $rootScope.$watch('wallet', function() { - $scope.walletSelection = false; - $scope.setWallets(); - }); - } - - // wallet list change - if ($rootScope.iden) { - var iden = $rootScope.iden; - iden.on('newWallet', function() { - $scope.walletSelection = false; - $scope.setWallets(); - }); - iden.on('walletDeleted', function(wid) { - if (wid == $rootScope.wallet.id) { - copay.logger.debug('Deleted focused wallet:', wid); - - // new focus - var newWid = $rootScope.iden.getLastFocusedWalletId(); - if (newWid && $rootScope.iden.getWalletById(newWid)) { - identityService.setFocusedWallet(newWid); - } else { - copay.logger.debug('No wallets'); - identityService.noFocusedWallet(newWid); - } - } - $scope.walletSelection = false; - $scope.setWallets(); - }); - } - }; - - $scope.setWallets = function() { - if (!$rootScope.iden) return; - var ret = _.filter($rootScope.iden.getWallets(), function(w) { - return w; - }); - $scope.wallets = _.sortBy(ret, 'name'); - }; - - $scope.openMenu = function() { - go.swipe(true); - }; -}); diff --git a/js/controllers/unsupported.js b/js/controllers/unsupported.js deleted file mode 100644 index 2617a81df..000000000 --- a/js/controllers/unsupported.js +++ /dev/null @@ -1,9 +0,0 @@ -'use strict'; - -angular.module('copayApp.controllers').controller('UnsupportedController', - function($scope, $location) { - if (localStorage && localStorage.length > 0) { - $location.path('/'); - } - } -); diff --git a/js/controllers/version.js b/js/controllers/version.js deleted file mode 100644 index 8c0cbc389..000000000 --- a/js/controllers/version.js +++ /dev/null @@ -1,34 +0,0 @@ -'use strict'; - -angular.module('copayApp.controllers').controller('VersionController', - function($scope, $rootScope, $http, $filter, notification) { - - var w = $rootScope.wallet; - - $scope.version = copay.version; - $scope.commitHash = copay.commitHash; - $scope.networkName = w ? w.getNetworkName() : ''; - if (_.isUndefined($rootScope.checkVersion)) - $rootScope.checkVersion = true; - - if ($rootScope.checkVersion) { - $rootScope.checkVersion = false; - $http.get('https://api.github.com/repos/bitpay/copay/tags').success(function(data) { - var toInt = function(s) { - return parseInt(s); - }; - var latestVersion = data[0].name.replace('v', '').split('.').map(toInt); - var currentVersion = copay.version.split('.').map(toInt); - var title = 'Copay ' + data[0].name + ' ' + $filter('translate')('available.'); - var content; - if (currentVersion[0] < latestVersion[0]) { - content = 'It\'s important that you update your wallet at https://copay.io'; - notification.version(title, content, true); - } else if (currentVersion[0] == latestVersion[0] && currentVersion[1] < latestVersion[1]) { - var content = 'Please update your wallet at https://copay.io'; - notification.version(title, content, false); - } - }); - } - - }); diff --git a/js/models/Async.js b/js/models/Async.js deleted file mode 100644 index 421d83b32..000000000 --- a/js/models/Async.js +++ /dev/null @@ -1,432 +0,0 @@ -'use strict'; - -var EventEmitter = require('events').EventEmitter; -var bitcore = require('bitcore'); -var log = require('../util/log'); -var AuthMessage = bitcore.AuthMessage; -var util = bitcore.util; -var nodeUtil = require('util'); -var extend = nodeUtil._extend; -var io = require('socket.io-client'); -var preconditions = require('preconditions').singleton(); - -function Network(opts) { - preconditions.checkArgument(opts); - preconditions.checkArgument(opts.url); - opts = opts || {}; - this.maxPeers = opts.maxPeers || 12; - this.url = opts.url; - this.secretNumber = opts.secretNumber; - this.cleanUp(); - - this.socketOptions = { - reconnection: true, - 'force new connection': true, - 'secure': this.url.indexOf('https') === 0, - }; - - if (opts.transports) { - this.socketOptions['transports'] = opts.transports; - } -} - -nodeUtil.inherits(Network, EventEmitter); - -Network.prototype.cleanUp = function() { - this.started = false; - this.connectedPeers = []; - this.peerId = null; - this.privkey = null; - this.key = null; - this.copayerId = null; - this.allowedCopayerIds = null; - this.isInboundPeerAuth = []; - this.copayerForPeer = {}; - this.criticalErr = ''; - if (this.socket) { - log.info('Async DISCONNECT'); - this.socket.disconnect(); - this.socket.removeAllListeners(); - this.socket = null; - } - this.removeAllListeners(); -}; - -Network.parent = EventEmitter; - -// Array helpers -Network._inArray = function(el, array) { - return array.indexOf(el) > -1; -}; - -Network._arrayPushOnce = function(el, array) { - var ret = false; - if (!Network._inArray(el, array)) { - array.push(el); - ret = true; - } - return ret; -}; - -Network._arrayRemove = function(el, array) { - var pos = array.indexOf(el); - if (pos >= 0) array.splice(pos, 1); - return array; -}; - -Network.prototype.connectedCopayers = function() { - var ret = []; - for (var i in this.connectedPeers) { - var copayerId = this.copayerForPeer[this.connectedPeers[i]]; - if (copayerId) ret.push(copayerId); - } - return ret; -}; - -Network.prototype._sendHello = function(copayerId, secretNumber) { - - this.send(copayerId, { - type: 'hello', - copayerId: this.copayerId, - secretNumber: secretNumber - }); -}; - -Network.prototype._sendRejectConnection = function(copayerId) { - - this.send(copayerId, { - type: 'rejectConnection', - copayerId: this.copayerId, - }); -}; - -Network.prototype._deletePeer = function(peerId) { - delete this.isInboundPeerAuth[peerId]; - delete this.copayerForPeer[peerId]; - this.connectedPeers = Network._arrayRemove(peerId, this.connectedPeers); -}; - -Network.prototype._addCopayer = function(copayerId) { - var peerId = this.peerFromCopayer(copayerId); - this._addCopayerMap(peerId, copayerId); - Network._arrayPushOnce(peerId, this.connectedPeers); -}; - -Network.prototype._addConnectedCopayer = function(copayerId) { - this._addCopayer(copayerId); - this.emit('connect', copayerId); -}; - -Network.prototype.getKey = function() { - preconditions.checkState(this.privkey || this.key); - if (!this.key) { - var key = new bitcore.Key(); - key.private = new Buffer(this.privkey, 'hex'); - key.regenerateSync(); - this.key = key; - } - return this.key; -}; - -//hex version of one's own nonce -Network.prototype.setHexNonce = function(networkNonce) { - if (networkNonce) { - if (networkNonce.length !== 16) - throw new Error('incorrect length of hex nonce'); - this.networkNonce = new Buffer(networkNonce, 'hex'); - } else - this.iterateNonce(); -}; - -//hex version of copayers' nonces -Network.prototype.setHexNonces = function(networkNonces) { - for (var i in networkNonces) { - if (!this.networkNonces) - this.networkNonces = {}; - if (networkNonces[i].length === 16) - this.networkNonces[i] = new Buffer(networkNonces[i], 'hex'); - } -}; - -//for oneself -Network.prototype.getHexNonce = function() { - return this.networkNonce.toString('hex'); -}; - -//for copayers -Network.prototype.getHexNonces = function() { - var networkNoncesHex = []; - for (var i in this.networkNonces) { - networkNoncesHex[i] = this.networkNonces[i].toString('hex'); - } - return networkNoncesHex; -}; - -Network.prototype.iterateNonce = function() { - if (!this.networkNonce || this.networkNonce.length !== 8) { - this.networkNonce = new Buffer(8); - this.networkNonce.fill(0); - } - //the first 4 bytes of a nonce is a unix timestamp in seconds - //the second 4 bytes is just an iterated "sub" nonce - //the whole thing is interpreted as one big endian number - var noncep1 = this.networkNonce.slice(0, 4); - noncep1.writeUInt32BE(Math.floor(Date.now() / 1000), 0); - var noncep2uint = this.networkNonce.slice(4, 8).readUInt32BE(0); - var noncep2 = this.networkNonce.slice(4, 8); - noncep2.writeUInt32BE(noncep2uint + 1, 0); - this.networkNonce = Buffer.concat([noncep1, noncep2], 8); - return this.networkNonce; -}; - -Network.prototype.decode = function(enc) { - var sender = enc.pubkey; - var key = this.getKey(); - var prevnonce = this.networkNonces ? this.networkNonces[sender] : undefined; - var opts = { - prevnonce: prevnonce - }; - var decoded = AuthMessage.decode(key, enc, opts); - - //if no error thrown in the last step, we can set the copayer's nonce - if (!this.networkNonces) - this.networkNonces = {}; - this.networkNonces[sender] = decoded.nonce; - - var payload = decoded.payload; - return payload; -}; - -Network.prototype._onMessage = function(enc) { - var sender = enc.pubkey; - try { - var payload = this.decode(enc); - } catch (e) { - this._deletePeer(sender); - return; - } - - if (this.ignoreMessageFromTs && this.ignoreMessageFromTs === enc.ts) { - log.debug('Ignoring trailing message. Ts:', enc.ts); - return; - } - - var self = this; - switch (payload.type) { - case 'hello': - if (typeof payload.secretNumber === 'undefined' || payload.secretNumber !== this.secretNumber) { - this._sendRejectConnection(sender); - this._deletePeer(enc.pubkey, 'incorrect secret number'); - return; - } - // if we locked allowed copayers, check if it belongs - if (this.allowedCopayerIds && !this.allowedCopayerIds[payload.copayerId]) { - this._sendRejectConnection(sender); - this._deletePeer(sender); - return; - } - //ensure claimed public key is actually the public key of the peer - //e.g., their public key should hash to be their peerId - if (sender !== payload.copayerId) { - this._sendRejectConnection(sender); - this._deletePeer(enc.pubkey, 'incorrect pubkey for peerId'); - return; - } - this._addConnectedCopayer(payload.copayerId); - break; - default: - this.emit('data', sender, payload, enc.ts); - } -}; - -Network.prototype._setupSocketHandlers = function(opts, cb) { - preconditions.checkState(this.socket); - log.debug('setting up connection', opts); - var self = this; - - self.socket.on('connect_error', function(m) { - - // If socket is not started, destroy it and emit and error - // If it is started, socket.io will try to reconnect. - if (!self.started) { - self.emit('connect_error'); - self.cleanUp(); - } - }); - - self.socket.on('subscribed', function(m) { - var fromTs = opts.syncedTimestamp || 0; - - // We ask for this message, and then ignore it, only to see if the - // server has erased our old messages. - - if (fromTs) { - self.ignoreMessageFromTs = fromTs; - } - log.info('Async: synchronizing from: ', fromTs); - self.socket.emit('sync', fromTs); - self.started = true; - }); - - - self.socket.on('message', function(m) { - // delay execution, to improve error handling - setTimeout(function() { - self._onMessage(m); - }, 1); - }); - self.socket.on('error', self._onError.bind(self)); - self.socket.on('no_messages', self.emit.bind(self, 'no_messages')); - self.socket.on('no messages', self.emit.bind(self, 'no_messages')); - self.socket.on('connect', function() { - var pubkey = self.getKey().public.toString('hex'); - log.debug('Async subscribing to pubkey:', pubkey); - - self.socket.emit('subscribe', pubkey); - - self.socket.on('disconnect', function() { - self.socket.emit('subscribe', pubkey); - }); - if (typeof cb === 'function') cb(); - }); -}; - -Network.prototype._onError = function(err) { - log.debug('RECV ERROR: ', err); - log.debug(err.stack); - this.criticalError = err.message; -}; - -Network.prototype.greet = function(copayerId, secretNumber) { - this._sendHello(copayerId, secretNumber); - var peerId = this.peerFromCopayer(copayerId); - this._addCopayerMap(peerId, copayerId); -}; - -Network.prototype._addCopayerMap = function(peerId, copayerId) { - if (!this.copayerForPeer[peerId]) { - if (Object.keys(this.copayerForPeer).length < this.maxPeers) { - this.copayerForPeer[peerId] = copayerId; - } - } -}; - -Network.prototype._setInboundPeerAuth = function(peerId) { - this.isInboundPeerAuth[peerId] = true; -}; - -Network.prototype.setCopayerId = function(copayerId) { - preconditions.checkState(!this.started, 'network already started: can not change peerId'); - - this.copayerId = copayerId; - this.copayerIdBuf = new Buffer(copayerId, 'hex'); - this.peerId = this.peerFromCopayer(this.copayerId); - this._addCopayerMap(this.peerId, copayerId); -}; - - -// TODO cache this. -Network.prototype.peerFromCopayer = function(hex) { - var SIN = bitcore.SIN; - return new SIN(new Buffer(hex, 'hex')).toString(); -}; - -Network.prototype.start = function(opts, openCallback) { - preconditions.checkArgument(opts); - preconditions.checkArgument(opts.privkey); - preconditions.checkArgument(opts.copayerId); - - if (this.started) { - log.debug('Async: Networing already started for this wallet.') - return openCallback(); - } - - this.privkey = opts.privkey; - this.setCopayerId(opts.copayerId); - this.maxPeers = opts.maxPeers || this.maxPeers; - - this.socket = this.createSocket(); - this._setupSocketHandlers(opts, openCallback); -}; - -Network.prototype.createSocket = function() { - log.debug('Async: Connecting to socket:', this.url); - return io.connect(this.url, this.socketOptions); -}; - -Network.prototype.getOnlinePeerIDs = function() { - return this.connectedPeers; -}; - - -Network.prototype.getCopayerIds = function() { - if (this.allowedCopayerIds) { - return Object.keys(this.allowedCopayerIds); - } else { - var copayerIds = []; - for (var peerId in this.copayerForPeer) { - copayerIds.push(this.copayerForPeer[peerId]); - } - return copayerIds; - } -}; - - -Network.prototype.send = function(dest, payload, cb) { - preconditions.checkState(this.socket); - preconditions.checkArgument(payload); - - var self = this; - if (!dest) { - dest = this.getCopayerIds(); - payload.isBroadcast = 1; - } - - if (typeof dest === 'string') - dest = [dest]; - - var l = dest.length; - var i = 0; - - for (var ii in dest) { - var to = dest[ii]; - if (to == this.copayerId) - continue; - - var message = this.encode(to, payload); - this.socket.emit('message', message); - } - - if (typeof cb === 'function') cb(); -}; - - -Network.prototype.encode = function(copayerId, payload, nonce) { - this.iterateNonce(); - var opts = { - nonce: nonce || this.networkNonce - }; - var copayerIdBuf = new Buffer(copayerId, 'hex'); - var message = AuthMessage.encode(copayerIdBuf, this.getKey(), payload, opts); - return message; -}; - -Network.prototype.isOnline = function() { - return !!this.socket; -}; - - -Network.prototype.lockIncommingConnections = function(allowedCopayerIdsArray) { - this.allowedCopayerIds = {}; - for (var i in allowedCopayerIdsArray) { - this.allowedCopayerIds[allowedCopayerIdsArray[i]] = true; - } -}; - -Network.prototype.setCopayers = function(copayersIdsArray) { - for (var i in copayersIdsArray) { - this._addCopayer(copayersIdsArray[i]); - } -}; - -module.exports = Network; diff --git a/js/models/Compatibility.js b/js/models/Compatibility.js deleted file mode 100644 index 94da67c59..000000000 --- a/js/models/Compatibility.js +++ /dev/null @@ -1,256 +0,0 @@ -'use strict'; - -var Identity = require('./Identity'); -var Wallet = require('./Wallet'); -var cryptoUtils = require('../util/crypto'); -var CryptoJS = require('node-cryptojs-aes').CryptoJS; -var sjcl = require('../../lib/sjcl'); -var log = require('../util/log'); -var preconditions = require('preconditions').instance(); -var _ = require('lodash'); - -var Compatibility = {}; -Compatibility.iterations = 100; -Compatibility.salt = 'mjuBtGybi/4='; - -/** - * Reads from localstorage wallets saved previously to 0.8 - */ -Compatibility._getWalletIds = function(cb) { - preconditions.checkArgument(cb); - var walletIds = []; - var uniq = {}; - var key; - for (key in localStorage) { - var split = key.split('::'); - if (split.length == 2) { - var walletId = split[0]; - - if (!walletId || walletId === 'nameFor' || walletId === 'lock' || walletId === 'wallet') { - continue; - } - - if (typeof uniq[walletId] === 'undefined') { - walletIds.push(walletId); - uniq[walletId] = 1; - } - } - } - return cb(walletIds); -}; - -/** - * @param {string} encryptedWallet - base64-encoded encrypted wallet - * @param {string} password - * @returns {Object} - */ -Compatibility.importLegacy = function(encryptedWallet, password) { - var passphrase = this.kdf(password); - var ret = Compatibility._decrypt(encryptedWallet, passphrase); - - if (!ret) return null; - return ret; -}; - -/** - * Decrypts using the CryptoJS library (unknown encryption schema) - * - * Don't use CryptoJS to encrypt. This still exists for compatibility reasons only. - */ -Compatibility._decrypt = function(base64, passphrase) { - var decryptedStr = null; - try { - var decrypted = CryptoJS.AES.decrypt(base64, passphrase); - if (decrypted) - decryptedStr = decrypted.toString(CryptoJS.enc.Utf8); - } catch (e) { - // Error while decrypting - return null; - } - return decryptedStr; -}; - -/** - * Reads an item from localstorage, decrypts it with passphrase - */ -Compatibility._read = function(k, passphrase, cb) { - preconditions.checkArgument(cb); - - var ret = localStorage.getItem(k); - if (!ret) return cb(null); - var ret = self._decrypt(ret, passphrase); - if (!ret) return cb(null); - - ret = ret.toString(CryptoJS.enc.Utf8); - ret = JSON.parse(ret); - return ret; -}; - -Compatibility.getWallets_Old = function(cb) { - preconditions.checkArgument(cb); - - var wallets = []; - var self = this; - - this._getWalletIds(function(ids) { - if (!ids.length) { - return cb([]); - } - - _.each(ids, function(id) { - var name = localStorage.getItem('nameFor::' + id); - if (name) { - wallets.push({ - id: id, - name: name, - }); - } - }); - return cb(wallets); - }); -}; - -Compatibility.getWallets2 = function(cb) { - var self = this; - var re = /wallet::([^_]+)(_?(.*))/; - var va = /^{+/; - - var key; - var keys = []; - for (key in localStorage) { - keys.push(key); - } - var wallets = _.compact(_.map(keys, function(key) { - if (key.indexOf('wallet::') !== 0) - return null; - var match = key.match(re); - var matchValue = localStorage[key].match(va); - if (match.length != 4) - return null; - if (matchValue) - return null; - return { - id: match[1], - name: match[3] ? match[3] : undefined, - value: localStorage[key] - }; - })); - - return cb(wallets); -}; - -/** - * Lists all wallets in localstorage - */ -Compatibility.listWalletsPre8 = function(cb) { - var self = this; - self.getWallets2(function(wallets) { - self.getWallets_Old(function(wallets2) { - var ids = _.pluck(wallets, 'id'); - _.each(wallets2, function(w) { - if (!_.contains(ids, w.id)) - wallets.push(w); - }); - return cb(wallets); - }); - }) -}; - -/** - * Retrieves a wallet that predates the 0.8 release - */ -Compatibility.readWalletPre8 = function(walletId, password, cb) { - var self = this; - var passphrase = cryptoUtils.kdf(password); - var obj = {}; - var key; - - for (key in localStorage) { - if (key.indexOf('wallet::' + walletId) !== -1) { - var ret = self._read(localStorage.getItem(key), passphrase); - if (err) return cb(err); - - _.each(Wallet.PERSISTED_PROPERTIES, function(p) { - obj[p] = ret[p]; - }); - - if (!_.any(_.values(obj))) - return cb(new Error('Wallet not found')); - - var w, err; - obj.id = walletId; - try { - w = self.fromObj(obj); - } catch (e) { - if (e && e.message && e.message.indexOf('MISSOPTS')) { - err = new Error('Could not read: ' + walletId); - } else { - err = e; - } - w = null; - } - return cb(err, w); - } - } -}; - -Compatibility.importEncryptedWallet = function(identity, cypherText, password, opts, cb) { - var crypto = (opts && opts.cryptoUtil) || cryptoUtils; - - var obj = crypto.decrypt(password, cypherText); - if (!obj) { - // 0.7.3 broken KDF - log.debug('Trying legacy encryption 0.7.2...'); - var passphrase = crypto.kdf(password, 'mjuBtGybi/4=', 100); - obj = crypto.decrypt(passphrase, cypherText); - } - - if (!obj) { - log.info("Could not decrypt, trying legacy.."); - obj = Compatibility.importLegacy(cypherText, password); - }; - - if (!obj) { - return cb('Could not decrypt', null); - } - - - try { - obj = JSON.parse(obj); - } catch (e) { - return cb('Could not read encrypted wallet', null); - } - return identity.importWalletFromObj(obj, opts, cb); -}; - -/** - * @desc Generate a WordArray expanding a password - * - * @param {string} password - the password to expand - * @returns WordArray 512 bits with the expanded key generated from password - */ -Compatibility.kdf = function(password) { - var hash = sjcl.hash.sha256.hash(sjcl.hash.sha256.hash(password)); - var salt = sjcl.codec.base64.toBits(this.salt); - - var crypto2 = function(key, salt, iterations, length, alg) { - return sjcl.codec.hex.fromBits(sjcl.misc.pbkdf2(key, salt, iterations, length * 8, - alg == 'sha1' ? function(key) { - return new sjcl.misc.hmac(key, sjcl.hash.sha1) - } : null - )) - }; - - var key512 = crypto2(hash, salt, this.iterations, 64, 'sha1'); - var sbase64 = sjcl.codec.base64.fromBits(sjcl.codec.hex.toBits(key512)); - return sbase64; -}; - -Compatibility.deleteOldWallet = function(walletObj) { -console.log('[Compatibility.js:249]',walletObj); //TODO - localStorage.removeItem('wallet::' + walletObj.id + '_' + walletObj.name); - log.info('Old wallet ' + walletObj.name + ' deleted: ' + walletObj.id); -}; - - -module.exports = Compatibility; diff --git a/js/models/HDParams.js b/js/models/HDParams.js deleted file mode 100644 index dba208e28..000000000 --- a/js/models/HDParams.js +++ /dev/null @@ -1,274 +0,0 @@ -'use strict'; - -// 83.8% typed (by google's closure-compiler account) - -var preconditions = require('preconditions').singleton(); -var HDPath = require('./HDPath'); -var _ = require('lodash'); - -/** - * @desc - * HDParams is a class that encapsulates information about the current indexes - * of a copayer - * - * When a copayer creates a new wallet, his receiveIndex gets updated, and an - * address is generated from everybody's public key, using this BIP32 path: - *
 m/copay'/{copayer}/0/{index} 
- * - * When a copayer generates a transaction proposal, his changeIndex gets - * updated, and all funds from that transaction proposal go to the multisig - * address generated from this BIP32 path for all the copayers: - *
 m/copay'/{copayer}/1/{changeIndex} 
- * - * There's a shared index, HDPath.SHARED_INDEX, that serves to - * generate addresses common to everybody. - * - * @TODO: Should opts.cosigner go? - * - * @constructor - * - * @param {Object} opts - options for the construction of this object - * @param {number=} opts.cosigner - backwards compatible index of a copayer - * @param {number} opts.copayerIndex - the copayer that generated this branch - * of addresses - * @param {number} opts.receiveIndex - the current index for a the last receive - * address generated for this copayer - * @param {number} opts.changeIndex - the current index for a the last change - * address generated for this copayer - */ -function HDParams(opts) { - opts = opts || {}; - - //opts.cosigner is for backwards compatibility only - - /** - * @public - * @type number - */ - this.copayerIndex = _.isUndefined(opts.copayerIndex) ? opts.cosigner : opts.copayerIndex; - /** - * @public - * @type number - */ - this.changeIndex = opts.changeIndex || 0; - /** - * @public - * @type number - */ - this.receiveIndex = opts.receiveIndex || 0; - - if (_.isUndefined(this.copayerIndex)) { - this.copayerIndex = HDPath.SHARED_INDEX; - } -} - -/** - * @desc - * Creates a set of HDParams, with one HDParams structure for each copayer and - * a shared path. - * - * @static - * @param {number} totalCopayers - the number of copayers in a wallet - * @returns {HDParams[]} a list of HDParams generated for a new empty wallet - */ -HDParams.init = function(totalCopayers) { - preconditions.shouldBeNumber(totalCopayers); - - var ret = [new HDParams({receiveIndex: 1})]; - for (var i = 0 ; i < totalCopayers ; i++) { - ret.push(new HDParams({copayerIndex: i})); - } - return ret; -}; - -/** - * @desc - * Generates a set of HDParams from a list with a object-serialized version of - * HDParams. - * - * @static - * @param {Object[]} hdParams - a list with objects - * @returns {HDParams[]} builds a HDParams for each object literal found in the list - */ -HDParams.fromList = function(hdParams) { - return hdParams.map(function(i) { return HDParams.fromObj(i); }); -}; - -/** - * @desc - * Generate a HDParams from an object. - * - * This essentialy only calls new HDParams(data), but it's the interface being - * used everywhere to encode/decode. - * - * @TODO: This should be clarified - Or abstracted away - as it's a pattern - * used in multiple places. - * - * @static - * @param {Object} data - a serialized version of HDParams - * @return {HDParams} - * @throws {BADDATA} - when the parameter data already is an instance of - * HDParams. - */ -HDParams.fromObj = function(data) { - if (data instanceof HDParams) { - throw new Error('BADDATA', 'bad data format: Did you use .toObj()?'); - } - return new HDParams(data); -}; - -/** - * @desc - * Serializes a list of HDParams to a list of "plain" objects, according to each - * element's toObj() method. - * - * @TODO: There should be a list of Classes that share this behaviour. - * - * @static - * @param {HDParams[]} hdParams - a list of HDParams objects. - * @returns {Array} an array with the toObj() serialization of each - * element in hdParams - */ -HDParams.serialize = function(hdParams) { - return hdParams.map(function(i) { return i.toObj(); }); -}; - -/** - * @desc - * Creates a new HDParams set with totalCopayers+1 elements. - * - * Sets the first (corresponding to the parameters for the shared addresses) - * HDParams object to match shared's values. Returns a serialized - * version of this set - * - *
- * var updateResult = HDParams.update({changeIndex: 1, receiveIndex: 2}, 5);
- * // All the following asserts succeed
- * assert(_.isArray(updateResult));
- * assert(_.all(updateResult, function (hd) { return !(hd instanceOf HDParams); }));
- * assert(_.size(updateResult) === 6);
- * assert(updateResult[0].changeIndex === 1);
- * assert(updateResult[0].receiveIndex === 2);
- * 
- * - * @TODO: This method is badly coded, it does something that is very specific - * and kind of strange. I couldn't figure out why would it be needed. - * - * @static - * @param {HDParams} shared - an instance of HDParams - * @param {number} totalCopayers - * @return {Object[]} - */ -HDParams.update = function(shared, totalCopayers) { - var hdParams = this.init(totalCopayers); - hdParams[0].changeIndex = shared.changeIndex; - hdParams[0].receiveIndex = shared.receiveIndex; - return this.serialize(hdParams); -}; - -/** - * @desc - * Serializes this object - * - * @TODO: I couldn't realize why would this be needed - calling, for example, - * JSON.stringify would have the same result on this object than on the - * original instance - * - * @returns {Object} a serialized version that should be equal (in a deep object - * comparison) to the this instance if passed to the - * HDParams() constructor. - */ -HDParams.prototype.toObj = function() { - return { - copayerIndex: this.copayerIndex, - changeIndex: this.changeIndex, - receiveIndex: this.receiveIndex - }; -}; - -/** - * @desc - * Throws an error if a given index falls out of the range for known addresses. - * - * @TODO: This is not a good pattern, exceptions should be for exceptional things. - * - * @param {number} index the index to check for - * @param {boolean} isChange whether to check for the change index or the - * receive address index - */ -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); - } -}; - -/** - * @desc - * Return this instance's changeIndex value - * - * @TODO: Somebody did a lot of java. Not sure if we need to be so verbose. If - * anything, let's just declare changeIndex as a read only variable - * @returns {number} this HDParams current index to generate a change address - */ -HDParams.prototype.getChangeIndex = function() { - return this.changeIndex; -}; - -/** - * @desc - * Return this instance's receiveIndex value - * - * @TODO: Somebody did a lot of java. Not sure if we need to be so verbose. If - * anything, let's just declare changeIndex as a read only variable - * @returns {number} this HDParams current index to generate a receive address - */ -HDParams.prototype.getReceiveIndex = function() { - return this.receiveIndex; -}; - -/** - * @desc - * Increment this instance's changeIndex or receiveIndex value - * - * @TODO: Somebody did a lot of java. Not sure if we need to be so verbose. - * - * @param {boolean} isChange - if true, change changeIndex - */ -HDParams.prototype.increment = function(isChange) { - if (isChange) { - this.changeIndex++; - } else { - this.receiveIndex++; - } -}; - -/** - * @desc - * Merge this instance with another HDParams instance. - * - * @TODO: Device a general approach to merges. - * - * @param {Object} inHDParams - the object to merge to - * @param {number} inHDParams.copayerIndex - the object to merge to - * @returns {boolean} true if this object has changed - */ -HDParams.prototype.merge = function(inHDParams) { - preconditions.shouldBeObject(inHDParams); - preconditions.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/HDPath.js b/js/models/HDPath.js deleted file mode 100644 index 7a03fa858..000000000 --- a/js/models/HDPath.js +++ /dev/null @@ -1,116 +0,0 @@ -'use strict'; - -// 90.2% typed (by google's closure-compiler account) - -var preconditions = require('preconditions').singleton(); -var _ = require('lodash'); - -/** - * @namespace - * @desc - * HDPath contains helper functions to handle BIP32 branches as - * Copay uses them. - * Based on https://github.com/maraoz/bips/blob/master/bip-NNNN.mediawiki - *
- * m / purpose' / copayerIndex / change:boolean / addressIndex
- * 
- */ -var HDPath = {}; - -/** - * @desc Copay's BIP45 purpose code - * @const - * @type number - */ -HDPath.PURPOSE = 45; - -/** - * @desc Maximum number for non-hardened values (BIP32) - * @const - * @type number - */ -HDPath.MAX_NON_HARDENED = 0x80000000 - 1; - -/** - * @desc Shared Index: used for creating addresses for no particular purpose - * @const - * @type number - */ -HDPath.SHARED_INDEX = HDPath.MAX_NON_HARDENED - 0; - -/** - * @desc ??? - * @const - * @type number - */ -HDPath.ID_INDEX = HDPath.MAX_NON_HARDENED - 1; - -/** - * @desc BIP45 prefix for COPAY - * @const - * @type string - */ -HDPath.BIP45_PUBLIC_PREFIX = 'm/' + HDPath.PURPOSE + '\''; - -/** - * @desc Retrieve a string to be used with bitcore representing a Copay branch - * @param {number} addressIndex - the last value of the HD derivation - * @param {boolean} isChange - whether this is a change address or a receive - * @param {number} copayerIndex - the index of the copayer in the pubkeyring - * @return {string} - the path for the HD derivation - */ -HDPath.Branch = function(addressIndex, isChange, copayerIndex) { - preconditions.checkArgument(_.isNumber(addressIndex)); - preconditions.checkArgument(_.isBoolean(isChange)); - - var ret = 'm/' + - (typeof copayerIndex !== 'undefined' ? copayerIndex : HDPath.SHARED_INDEX) + '/' + - (isChange ? 1 : 0) + '/' + - addressIndex; - return ret; -}; - -/** - * @desc ??? - * @param {number} addressIndex - the last value of the HD derivation - * @param {boolean} isChange - whether this is a change address or a receive - * @param {number} copayerIndex - the index of the copayer in the pubkeyring - * @return {string} - the path for the HD derivation - */ -HDPath.FullBranch = function(addressIndex, isChange, copayerIndex) { - preconditions.checkArgument(_.isNumber(addressIndex)); - preconditions.checkArgument(_.isBoolean(isChange)); - - var sub = HDPath.Branch(addressIndex, isChange, copayerIndex); - sub = sub.substring(2); - return HDPath.BIP45_PUBLIC_PREFIX + '/' + sub; -}; - -/** - * @desc - * Decompose a string and retrieve its arguments as if it where a Copay address. - * @param {string} path - the HD path - * @returns {Object} an object with three keys: addressIndex, isChange, and - * copayerIndex - */ -HDPath.indexesForPath = function(path) { - preconditions.checkArgument(_.isString(path)); - - var s = path.split('/'); - return { - isChange: s[3] === '1', - addressIndex: parseInt(s[4], 10), - copayerIndex: parseInt(s[2], 10) - }; -}; - -/** - * @desc The ID for a shared branch - */ -HDPath.IdFullBranch = HDPath.FullBranch(0, false, HDPath.ID_INDEX); -/** - * @desc Partial ID for a shared branch - */ -HDPath.IdBranch = HDPath.Branch(0, false, HDPath.ID_INDEX); - -module.exports = HDPath; diff --git a/js/models/Identity.js b/js/models/Identity.js deleted file mode 100644 index 8cda88980..000000000 --- a/js/models/Identity.js +++ /dev/null @@ -1,905 +0,0 @@ -'use strict'; -var _ = require('lodash'); -var preconditions = require('preconditions').singleton(); -var inherits = require('inherits'); -var events = require('events'); -var async = require('async'); - -var bitcore = require('bitcore'); - -var TxProposals = require('./TxProposals'); -var PublicKeyRing = require('./PublicKeyRing'); -var PrivateKey = require('./PrivateKey'); -var Wallet = require('./Wallet'); -var PluginManager = require('./PluginManager'); -var Async = require('./Async'); -var cryptoUtil = require('../util/crypto'); -var log = require('../util/log'); -var version = require('../../version').version; - -/** - * @desc - * Identity - stores the state for a wallet in creation - * - * @param {Object} opts - configuration for this wallet - * @param {string} opts.fullName - * @param {string} opts.email - * @param {string} opts.password - * @param {string} opts.storage - * @param {string} opts.pluginManager - * @param {Object} opts.walletDefaults - * @param {string} opts.version - * @param {Object} opts.wallets - * @param {Object} opts.network - * @param {string} opts.network.testnet - * @param {string} opts.network.livenet - * @constructor - */ -function Identity(opts) { - preconditions.checkArgument(opts); - - opts = _.extend({}, opts); - this.networkOpts = { - 'livenet': opts.network.livenet, - 'testnet': opts.network.testnet, - }; - this.blockchainOpts = { - 'livenet': opts.network.livenet, - 'testnet': opts.network.testnet, - }; - - this.fullName = opts.fullName || opts.email; - this.email = opts.email; - this.password = opts.password; - - this.storage = opts.storage || opts.pluginManager.get('DB'); - this.storage.setCredentials(this.email, this.password, {}); - - this.walletDefaults = opts.walletDefaults || {}; - this.version = opts.version || version; - - this.walletIds = opts.walletIds || []; - this.wallets = opts.wallets || {}; - this.focusedTimestamps = opts.focusedTimestamps || {}; - this.backupNeeded = opts.backupNeeded || false; - -}; - - -inherits(Identity, events.EventEmitter); - -Identity.getStoragePrefix = function() { - return 'profile::'; -}; - -Identity.getKeyForEmail = function(email) { - return Identity.getStoragePrefix() + bitcore.util.sha256ripe160(email).toString('hex'); -}; - -Identity.prototype.getChecksumForStorage = function() { - return JSON.stringify(_.sortBy(this.walletIds)); -}; - -Identity.prototype.getId = function() { - return Identity.getKeyForEmail(this.email); -}; - -Identity.prototype.getName = function() { - return this.fullName || this.email; -}; - -/** - * Creates an Identity - * - * @param opts - * @param cb - * @return {undefined} - */ -Identity.create = function(opts, cb) { - opts = _.extend({ - backupNeeded: true - }, opts); - - var iden = new Identity(opts); - iden.store(_.extend(opts, { - failIfExists: true - }), function(err) { - if (err) return cb(err); - return cb(null, iden); - }); -}; - -Identity.prototype.resendVerificationEmail = function(cb) { - var self = this; - - preconditions.checkArgument(_.isFunction(cb)); - preconditions.checkState(_.isFunction(self.storage.resendVerificationEmail)); - - self.storage.resendVerificationEmail(cb); -}; - - -/** - * Open an Identity from the given storage. - * - * After opening a profile, and setting its wallet event handlers, - * the client must run .netStart on each - * (probably on iden's newWallet handler - * - * @param {Object} opts - * @param {Object} opts.storage - * @param {string} opts.email - * @param {string} opts.password - * @param {Function} cb - */ -Identity.open = function(opts, cb) { - preconditions.checkArgument(_.isObject(opts)); - preconditions.checkArgument(_.isFunction(cb)); - - var storage = opts.storage || opts.pluginManager.get('DB'); - storage.setCredentials(opts.email, opts.password, opts); - storage.getItem(Identity.getKeyForEmail(opts.email), function(err, data, headers) { - var exported; - if (err) { - return cb(err); - } - try { - exported = JSON.parse(data); - } catch (e) { - return cb(e); - } - return cb(null, new Identity(_.extend(opts, exported)), headers); - }); -}; - -Identity.prototype.verifyChecksum = function(cb) { - var self = this; - - self.storage.getItem(Identity.getKeyForEmail(self.email), function(err, data, headers) { - var iden; - if (err) return cb(err); - try { - iden = JSON.parse(data); - } catch (e) { - return cb(e); - } - return cb(null, self.getChecksumForStorage() == self.getChecksumForStorage.call(iden)); - }); -}; - - -/** - * @param {string} walletId - * @returns {Wallet} - */ -Identity.prototype.getWalletById = function(walletId) { - return this.wallets[walletId]; -}; - -/** - * @returns {Wallet[]} - */ -Identity.prototype.getWallets = function() { - return _.values(this.wallets); -}; - -/** - * addWallet - * - * @param w - */ -Identity.prototype.addWallet = function(w) { - this.wallets[w.getId()] = w; - this.walletIds = _.union(this.walletIds, [w.getId()]); -}; - -/** - * @desc Deletes a wallet. This involves removing it from the storage instance - * - * @param {string} walletId - * @callback cb - * @return {err} - */ -Identity.prototype.deleteWallet = function(walletId, cb) { - preconditions.checkArgument(_.isString(walletId)); - var self = this; - - self.verifyChecksum(function(err, match) { - if (err) return cb(err); - if (!match) return cb('The profile is out of sync. Please re-login to get the latest changes.'); - - var w = self.getWalletById(walletId); - w.close(); - - delete self.wallets[walletId]; - delete self.focusedTimestamps[walletId]; - self.walletIds = _.without(self.walletIds, walletId); - - self.storage.removeItem(Wallet.getStorageKey(walletId), function(err) { - if (err) return cb(err); - self.emitAndKeepAlive('walletDeleted', walletId); - if (!self.walletIds.length) { - self.emitAndKeepAlive('noWallets') - } - self.store({ - noWallets: true - }, cb); - }); - }); -}; - - -/** - * readAndBindWallet - * - * @param {string} wid walletId to be readed - * @param {function} cb - * - */ -Identity.prototype.readAndBindWallet = function(walletId, cb) { - var self = this; - self.retrieveWalletFromStorage(walletId, {}, function(error, wallet) { - if (!error) { - self.addWallet(wallet); - self.bindWallet(wallet); - } - return cb(error); - }); -}; - - -Identity.prototype.emitAndKeepAlive = function(args) { - var args = Array.prototype.slice.call(arguments); - log.debug('Ident Emitting:', args); - //this.keepAlive(); // TODO - this.emit.apply(this, arguments); -}; - - -/** - * @desc open profile's wallets. Call it AFTER setting - * the proper even listeners. no callback. - * - */ -Identity.prototype.openWallets = function() { - var self = this; - - - if (_.isEmpty(self.walletIds)) { - self.emitAndKeepAlive('noWallets') - return; - } - - // First read the lastFocused wallet - self.walletIds.sort(function(a, b) { - var va = self.focusedTimestamps[a] || 0; - var vb = self.focusedTimestamps[b] || 0; - - return va < vb ? 1 : (va === vb ? 0 : -1); - }); - - // opens the wallets, in the order they were last accessed. Emits open events (newWallet) - async.eachSeries(self.walletIds, function(walletId, a_cb) { - self.readAndBindWallet(walletId, a_cb); - }); -}; - -/** - * @param {string} walletId - * @param {} opts - * opts.importWallet - * @param {Function} cb - */ -Identity.prototype.retrieveWalletFromStorage = function(walletId, opts, cb) { - var self = this; - - var importFunction = opts.importWallet || Wallet.fromUntrustedObj; - - this.storage.getItem(Wallet.getStorageKey(walletId), function(error, walletData) { - if (error) { - return cb(error); - } - try { - log.info('## OPENING Wallet:', walletId); - if (_.isString(walletData)) { - walletData = JSON.parse(walletData); - } - var readOpts = { - networkOpts: self.networkOpts, - blockchainOpts: self.blockchainOpts, - skipFields: [] - }; - } catch (e) { - log.debug("ERROR: ", e.message); - if (e && e.message && e.message.indexOf('MISSOPTS') !== -1) { - return cb(new Error('WERROR: Could not read: ' + walletId + ': ' + e.message)); - } else { - return cb(e); - } - } - return cb(null, importFunction(walletData, readOpts)); - }); -}; - -/** - * @param {Wallet} wallet - * @param {Function} cb - */ -Identity.prototype.storeWallet = function(wallet, cb) { - var self = this; - - preconditions.checkArgument(wallet && _.isObject(wallet)); - - wallet.setVersion(this.version); - var val = wallet.toObj(); - var key = wallet.getStorageKey(); - log.debug('Storing wallet:' + wallet.getName()); - - this.storage.setItem(key, val, function(err) { - if (err) { - log.error('Wallet:' + wallet.getName() + ' could not be stored:', err); - log.error('Wallet:' + wallet.getName() + ' Size:', JSON.stringify(wallet.sizes())); - - if (err.match('OVERQUOTA')) { - self.emitAndKeepAlive('walletStorageError', wallet.getId(), 'Storage limits on remote server exceeded'); - } else { - self.emitAndKeepAlive('walletStorageError', wallet.getId(), err); - } - } - if (cb) - return cb(err); - }); -}; - - -/** - * @param {Identity} identity - * @param {Wallet} wallet - * @param {Function} cb - */ -Identity.storeWalletDebounced = _.debounce(function(identity, wallet, cb) { - identity.storeWallet(wallet, cb); -}, 3000); - - - -Identity.prototype.toObj = function() { - return _.pick(this, 'walletIds', 'version', 'fullName', 'password', 'email', 'backupNeeded', 'focusedTimestamps'); -}; - -Identity.prototype.exportEncryptedWithWalletInfo = function(opts) { - var crypto = opts.cryptoUtil || cryptoUtil; - - return crypto.encrypt(this.password, this.exportWithWalletInfo(opts)); -}; - -Identity.prototype.setBackupNeeded = function(backupNeeded) { - var self = this; - - self.backupNeeded = !!backupNeeded; - - self.verifyChecksum(function(err, match) { - if (err) { - log.error(err); - return; - } - if (!match) { - log.error('The profile is out of sync. Please re-login to get the latest changes.'); - return; - } - - self.store({ - noWallets: true - }, function() {}); - }); -} - -Identity.prototype.exportWithWalletInfo = function(opts) { - return _.extend({ - wallets: _.map(this.getWallets(), function(wallet) { - return wallet.toObj(); - }) - }, - _.pick(this, 'version', 'fullName', 'password', 'email', 'backupNeeded') - ); -}; - -/** - * @param {Object} opts - * @param {Function} cb - */ -Identity.prototype.store = function(opts, cb) { - var self = this; - opts = opts || {}; - - var storeFunction = opts.failIfExists ? self.storage.createItem : self.storage.setItem; - - storeFunction.call(self.storage, this.getId(), this.toObj(), function(err) { - if (err) return cb(err); - - if (opts.noWallets) return cb(); - - async.each(self.getWallets(), function(wallet, in_cb) { - self.storeWallet(wallet, in_cb); - }, cb); - }); -}; - -/** - * @param {Object} opts - * @param {Function} cb - */ -Identity.prototype.remove = function(opts, cb) { - log.debug('Deleting profile'); - - var self = this; - opts = opts || {}; - - async.each(self.getWallets(), function(w, cb) { - w.close(); - self.storage.removeItem(Wallet.getStorageKey(w.getId()), function(err) { - if (err) return cb(err); - cb(); - }); - }, function(err) { - if (err) return cb(err); - - self.storage.removeItem(self.getId(), function(err) { - if (err) return cb(err); - - self.storage.clear(function(err) { - if (err) return cb(err); - - self.emitAndKeepAlive('closed'); - return cb(); - }); - }); - }); -}; - -Identity.prototype._cleanUp = function() { - var self = this; - - _.each(this.getWallets(), function(w) { - w.close(); - delete self.wallets[w.getId()]; - }); -}; - -/** - * @desc Closes the wallet and disconnects all services - */ -Identity.prototype.close = function(cb) { - var self = this; - - function doClose() { - self._cleanUp(); - self.emitAndKeepAlive('closed'); - if (cb) return cb(); - }; - - self.verifyChecksum(function(err, match) { - if (!err && match) { - self.store({ - noWallets: true, - }, function(err) { - return doClose(); - }); - } else { - return doClose(); - } - }); -}; - - -Identity.prototype.importWalletFromObj = function(obj, opts, cb) { - var self = this; - preconditions.checkArgument(cb); - - self.verifyChecksum(function(err, match) { - if (err) return cb(err); - if (!match) return cb('The profile is out of sync. Please re-login to get the latest changes.'); - - var importFunction = opts.importWallet || Wallet.fromUntrustedObj; - - var readOpts = { - networkOpts: self.networkOpts, - blockchainOpts: self.blockchainOpts, - skipFields: opts.skipFields, - }; - - var w = importFunction(obj, readOpts); - if (!w) return cb(new Error('Could not decrypt')); - log.debug('Wallet decrypted:' + w.getName()); - - self._checkVersion(w.version); - log.debug('Updating Indexes for wallet:' + w.getName()); - w.updateIndexes(function(err) { - log.debug('Adding wallet to profile:' + w.getName()); - self.storeWallet(w, function(err) { - if (err) return cb(err); - - self.addWallet(w); - self.updateFocusedTimestamp(w.getId()); - self.bindWallet(w); - - self.backupNeeded = true; - self.store({ - noWallets: true, - }, function(err) { - return cb(err); - }); - }); - }); - }); -}; - - -Identity.prototype.importMultipleWalletsFromObj = function(objs, opts) { - var self = this; - opts = opts || {}; - - async.eachSeries(objs, function(walletData, cb) { - if (!walletData) - return cb(); - self.importWalletFromObj(walletData, opts, cb); - }); -}; - - -/** - * @param {Wallet} wallet - * @param {Function} cb - */ -Identity.prototype.closeWallet = function(wallet, cb) { - preconditions.checkState(wallet, 'Wallet not found'); - - var self = this; - wallet.close(function(err) { - return cb(err); - }); -}; - -Identity.importFromEncryptedFullJson = function(ejson, password, opts, cb) { - var crypto = opts.cryptoUtil || cryptoUtil; - - var str = crypto.decrypt(password, ejson); - if (!str) { - // 0.7.3 broken KDF - log.debug('Trying legacy encryption...'); - var passphrase = crypto.kdf(password, 'mjuBtGybi/4=', 100); - str = crypto.decrypt(passphrase, ejson); - } - - if (!str) - return cb('BADSTR'); - - return Identity.importFromFullJson(str, password, opts, cb); -}; - -Identity.importFromFullJson = function(str, password, opts, cb) { - preconditions.checkArgument(str); - var json; - try { - json = JSON.parse(str); - } catch (e) { - return cb('BADSTR: Unable to retrieve json from string', str); - } - - var email = json.email; - - opts.email = email; - opts.password = password; - - if (!email) - return cb('BADSTR'); - - var iden = new Identity(opts); - - opts.failIfExists = true; - - json.wallets = json.wallets || {}; - - iden.store(opts, function(err) { - if (err) return cb(err); //profile already exists - - return cb(null, iden, json.wallets); - }); -}; - - -/** - * @desc binds a wallet's events and emits 'newWallet' - * @param {string} walletId Wallet id to be binded - * @emits newWallet (walletId) - */ -Identity.prototype.bindWallet = function(w) { - preconditions.checkArgument(w && this.getWalletById(w.getId())); - - var self = this; - log.debug('Binding wallet:' + w.getName()); - - w.on('txProposalsUpdated', function() { - Identity.storeWalletDebounced(self, w); - }); - w.on('paymentAck', function() { - Identity.storeWalletDebounced(self, w); - }); - w.on('newAddresses', function() { - Identity.storeWalletDebounced(self, w); - }); - w.on('settingsUpdated', function() { - Identity.storeWalletDebounced(self, w); - }); - w.on('txProposalEvent', function() { - Identity.storeWalletDebounced(self, w); - }); - w.on('addressBookUpdated', function() { - Identity.storeWalletDebounced(self, w); - }); - w.on('publicKeyRingUpdated', function() { - Identity.storeWalletDebounced(self, w); - }); - w.on('ready', function() { - Identity.storeWalletDebounced(self, w); - }); - - this.emitAndKeepAlive('newWallet', w.getId()); -}; - -/** - * @desc This method prepares options for a new Wallet - * - * @param {Object} opts - * @param {string} opts.id - * @param {PrivateKey=} opts.privateKey - * @param {string=} opts.privateKeyHex - * @param {number} opts.requiredCopayers - * @param {number} opts.totalCopayers - * @param {PublicKeyRing=} opts.publicKeyRing - * @param {string} opts.nickname - * @param {string} opts.password - * @param {boolean} opts.spendUnconfirmed this.walletDefaults.spendUnconfirmed - * @param {number} opts.reconnectDelay time in milliseconds - * @param {number=} opts.version - * @param {callback} opts.version - * @return {Wallet} - */ -Identity.prototype.createWallet = function(opts, cb) { - preconditions.checkArgument(cb); - - var self = this; - - self.verifyChecksum(function(err, match) { - if (err) return cb(err); - if (!match) return cb('The profile is out of sync. Please re-login to get the latest changes.'); - - opts = opts || {}; - opts.networkName = opts.networkName || 'testnet'; - - log.debug('### CREATING NEW WALLET.' + (opts.id ? ' USING ID: ' + opts.id : ' NEW ID') + (opts.privateKey ? ' USING PrivateKey: ' + opts.privateKey.getId() : ' NEW PrivateKey')); - - var privOpts = { - networkName: opts.networkName, - }; - - if (opts.privateKeyHex && opts.privateKeyHex.length > 1) { - privOpts.extendedPrivateKeyString = opts.privateKeyHex; - } - - opts.privateKey = opts.privateKey || new PrivateKey(privOpts); - - var requiredCopayers = opts.requiredCopayers || self.walletDefaults.requiredCopayers; - var totalCopayers = opts.totalCopayers || self.walletDefaults.totalCopayers; - opts.lockTimeoutMin = self.walletDefaults.idleDurationMin; - - opts.publicKeyRing = opts.publicKeyRing || new PublicKeyRing({ - networkName: opts.networkName, - requiredCopayers: requiredCopayers, - totalCopayers: totalCopayers, - }); - opts.publicKeyRing.addCopayer( - opts.privateKey.deriveBIP45Branch().extendedPublicKeyString(), - opts.nickname || self.getName() - ); - log.debug('\t### PublicKeyRing Initialized'); - - opts.txProposals = opts.txProposals || new TxProposals({ - networkName: opts.networkName, - }); - var walletClass = opts.walletClass || Wallet; - - log.debug('\t### TxProposals Initialized'); - - - opts.networkOpts = self.networkOpts; - opts.blockchainOpts = self.blockchainOpts; - - opts.spendUnconfirmed = opts.spendUnconfirmed || self.walletDefaults.spendUnconfirmed; - opts.reconnectDelay = opts.reconnectDelay || self.walletDefaults.reconnectDelay; - opts.requiredCopayers = requiredCopayers; - opts.totalCopayers = totalCopayers; - opts.version = opts.version || self.version; - - var w = new walletClass(opts); - - if (self.getWalletById(w.getId())) { - return cb('walletAlreadyExists'); - } - - self.storeWallet(w, function(err) { - if (err) return cb(err); - - self.addWallet(w); - self.updateFocusedTimestamp(w.getId()); - self.bindWallet(w); - - self.backupNeeded = true; - self.store({ - noWallets: true, - }, function(err) { - return cb(err, w); - }); - }); - }); -}; - -/** - * @desc Checks if a version is compatible with the current version - * @param {string} inVersion - a version, with major, minor, and revision, period-separated (x.y.z) - * @throws {Error} if there's a major version difference - */ -Identity.prototype._checkVersion = function(inVersion) { - if (inVersion) { - var thisV = this.version.split('.'); - var thisV0 = parseInt(thisV[0]); - var inV = inVersion.split('.'); - var inV0 = parseInt(inV[0]); - } - - //We only check for major version differences - if (thisV0 < inV0) { - throw new Error('Major difference in software versions' + - '. Received:' + inVersion + - '. Current version:' + this.version + - '. Aborting.'); - } -}; - - -/** - * @desc Pass through to {@link Wallet#secret} - */ -Identity.prototype.decodeSecret = function(secret) { - try { - return Wallet.decodeSecret(secret); - } catch (e) { - return false; - } -}; - -/** - * getLastFocusedWalletId - * - * @return {string} walletId - */ -Identity.prototype.getLastFocusedWalletId = function() { - if (this.walletIds.length == 0) return undefined; - - var max = _.max(this.focusedTimestamps); - - if (!max) - return this.walletIds[0]; - - return _.findKey(this.focusedTimestamps, function(ts) { - return ts == max; - }) || this.walletIds[0]; -}; - -Identity.prototype.updateFocusedTimestamp = function(wid) { - preconditions.checkArgument(wid && this.getWalletById(wid)); - this.focusedTimestamps[wid] = Date.now(); -}; - -/** - * @callback walletCreationCallback - * @param {?} err - an error, if any, that happened during the wallet creation - * @param {Wallet=} wallet - the wallet created - */ - -/** - * @desc Start the network functionality. - * - * Start up the Network instance and try to join a wallet defined by the - * parameter secret using the parameter nickname. Encode - * information locally using passphrase. privateHex is the - * private extended master key. cb has two params: error and wallet. - * - * @param {object} opts - * @param {string} opts.secret - the wallet secret - * @param {string} opts.nickname - a nickname for the current user - * @param {string} opts.privateHex - the private extended master key - * @param {walletCreationCallback} cb - a callback - */ -Identity.prototype.joinWallet = function(opts, cb) { - preconditions.checkArgument(opts); - preconditions.checkArgument(opts.secret); - preconditions.checkArgument(cb); - var self = this; - var decodedSecret = this.decodeSecret(opts.secret); - if (!decodedSecret || !decodedSecret.networkName || !decodedSecret.pubKey) { - return cb('badSecret'); - } - - var privOpts = { - networkName: decodedSecret.networkName, - }; - - if (opts.privateHex && opts.privateHex.length > 1) { - privOpts.extendedPrivateKeyString = opts.privateHex; - } - - //Create our PrivateK - var privateKey = new PrivateKey(privOpts); - log.debug('\t### PrivateKey Initialized'); - var joinOpts = { - copayerId: privateKey.getId(), - privkey: privateKey.getIdPriv(), - key: privateKey.getIdKey(), - secretNumber: decodedSecret.secretNumber, - }; - - - var joinNetwork = opts.Async || new Async(this.networkOpts[decodedSecret.networkName]); - - // This is a hack to reconize if the connection was rejected or the peer wasn't there. - var connectedOnce = false; - joinNetwork.on('connected', function(sender, data) { - connectedOnce = true; - }); - - joinNetwork.on('connect_error', function() { - return cb('connectionError'); - }); - - joinNetwork.on('serverError', function() { - return cb('joinError'); - }); - - joinNetwork.start(joinOpts, function() { - - joinNetwork.greet(decodedSecret.pubKey, joinOpts.secretNumber); - joinNetwork.on('data', function(sender, data) { - if (data.type === 'walletId' && data.opts) { - if (!data.networkName || data.networkName !== decodedSecret.networkName) { - return cb('badNetwork'); - } - data.opts.networkName = data.networkName; - - var walletOpts = _.clone(data.opts); - walletOpts.id = data.walletId; - walletOpts.network = joinNetwork; - - walletOpts.privateKey = privateKey; - walletOpts.nickname = opts.nickname || self.getName(); - - if (opts.password) - walletOpts.password = opts.password; - - self.createWallet(walletOpts, function(err, w) { - if (w) { - w.sendWalletReady(decodedSecret.pubKey); - } else { - if (!err) { - err = 'walletFull'; - } - } - return cb(err, w); - }); - } - }); - }); -}; - - -module.exports = Identity; diff --git a/js/models/Insight.js b/js/models/Insight.js deleted file mode 100644 index b642a1c21..000000000 --- a/js/models/Insight.js +++ /dev/null @@ -1,346 +0,0 @@ -'use strict'; - -var util = require('util'); -var async = require('async'); -var request = require('request'); -var io = require('socket.io-client'); -var _ = require('lodash'); -var EventEmitter = require('events').EventEmitter; -var preconditions = require('preconditions').singleton(); - -var bitcore = require('bitcore'); - -var log = require('../util/log.js'); - - -/* - This class lets interface with the blockchain, making general queries and - subscribing to transactions on addresses and blocks. - - Opts: - - url - - reconnection (optional) - - reconnectionDelay (optional) - - Events: - - tx: activity on subscribed address. - - block: a new block that includes a subscribed address. - - connect: the connection with the blockchain is ready. - - disconnect: the connection with the blochckain is unavailable. -*/ - -var Insight = function(opts) { - preconditions.checkArgument(opts) - .shouldBeObject(opts) - .checkArgument(opts.url) - - this.status = this.STATUS.DISCONNECTED; - this.subscribed = {}; - this.listeningBlocks = false; - - this.url = opts.url; - this.opts = { - 'reconnection': opts.reconnection || true, - 'reconnectionDelay': opts.reconnectionDelay || 1000, - 'secure': opts.url.indexOf('https') === 0 - }; - - - if (opts.transports) { - this.opts['transports'] = opts.transports; - } - - this.socket = this.getSocket(); -} - -Insight.setCompleteUrl = function(uri) { - - if (!uri) return uri; - - var re = /^(?:(?![^:@]+:[^:@\/]*@)(http|https|ws|wss):\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?((?:[a-f0-9]{0,4}:){2,7}[a-f0-9]{0,4}|[^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/; - - var parts = [ - 'source', 'protocol', 'authority', 'userInfo', 'user', 'password', 'host', 'port', 'relative', 'path', 'directory', 'file', 'query', 'anchor' - ]; - - function parseuri(str) { - var m = re.exec(str || ''), - uri = {}, - i = 14; - - while (i--) { - uri[parts[i]] = m[i] || ''; - } - - return uri; - }; - - var opts_host; - var opts_secure; - var opts_port; - var opts_protocol; - if (uri) { - uri = parseuri(uri); - opts_host = uri.host; - opts_protocol = uri.protocol; - opts_secure = uri.protocol == 'https' || uri.protocol == 'wss'; - opts_port = uri.port; - } - - var this_secure = null != opts_secure ? opts_secure : - ('https:' == location.protocol); - - var opts_hostname; - if (opts_host) { - var pieces = opts_host.split(':'); - opts_hostname = pieces.shift(); - if (pieces.length) opts_port = pieces.pop(); - } - - var this_port = opts_port || - (this_secure ? 443 : 80); - - var newUri = opts_protocol + '://' + opts_host + ':' + this_port; - - return newUri; -} - -util.inherits(Insight, EventEmitter); - -Insight.prototype.STATUS = { - CONNECTED: 'connected', - DISCONNECTED: 'disconnected', - DESTROYED: 'destroyed' -} - -/** @private */ -Insight.prototype.subscribeToBlocks = function() { - var socket = this.getSocket(); - if (this.listeningBlocks || !socket.connected) return; - - var self = this; - socket.on('block', function(blockHash) { - self.emit('block', blockHash); - }); - this.listeningBlocks = true; -} - -/** @private */ -Insight.prototype._getSocketIO = function(url, opts) { - log.debug('Insight: Connecting to socket:', this.url); - return io(this.url, this.opts); -}; - - -Insight.prototype._setMainHandlers = function(url, opts) { - // Emmit connection events - var self = this; - this.socket.on('connect', function() { - self.status = self.STATUS.CONNECTED; - self.subscribeToBlocks(); - self.emit('connect', 0); - }); - - this.socket.on('connect_error', function() { - if (self.status != self.STATUS.CONNECTED) return; - self.status = self.STATUS.DISCONNECTED; - self.emit('disconnect'); - }); - - this.socket.on('connect_timeout', function() { - if (self.status != self.STATUS.CONNECTED) return; - self.status = self.STATUS.DISCONNECTED; - self.emit('disconnect'); - }); - - this.socket.on('reconnect', function(attempt) { - if (self.status != self.STATUS.DISCONNECTED) return; - self.emit('reconnect', attempt); - self.reSubscribe(); - self.status = self.STATUS.CONNECTED; - }); -}; - - -/** @private */ -Insight.prototype.getSocket = function() { - - if (!this.socket) { - this.socket = this._getSocketIO(this.url, this.opts); - this._setMainHandlers(); - } - return this.socket; -} - -/** @private */ -Insight.prototype.request = function(path, cb) { - preconditions.checkArgument(path).shouldBeFunction(cb); - request(this.url + path, cb); -} - -/** @private */ -Insight.prototype.requestPost = function(path, data, cb) { - preconditions.checkArgument(path).checkArgument(data).shouldBeFunction(cb); - request({ - method: "POST", - url: this.url + path, - json: data - }, cb); -} - -Insight.prototype.destroy = function() { - var socket = this.getSocket(); - this.socket.disconnect(); - this.socket.removeAllListeners(); - this.socket = null; - this.subscribed = {}; - this.status = this.STATUS.DESTROYED; - this.removeAllListeners(); -}; - -Insight.prototype.subscribe = function(addresses) { - addresses = Array.isArray(addresses) ? addresses : [addresses]; - var self = this; - - function handlerFor(self, address) { - return function(txid) { - // verify the address is still subscribed - if (!self.subscribed[address]) return; - - self.emit('tx', { - address: address, - txid: txid - }); - } - } - - var s = self.getSocket(); - addresses.forEach(function(address) { - preconditions.checkArgument(new bitcore.Address(address).isValid()); - - // skip already subscibed - if (!self.subscribed[address]) { - var handler = handlerFor(self, address); - self.subscribed[address] = handler; -// log.debug('Subscribe to: ', address); - s.emit('subscribe', address); - s.on(address, handler); - } - }); -}; - -Insight.prototype.getSubscriptions = function(addresses) { - return this.subscribed; -} - - -Insight.prototype.reSubscribe = function() { - log.debug('insight reSubscribe'); - var allAddresses = Object.keys(this.subscribed); - this.subscribed = {}; - var s = this.socket; - if (s) { - s.removeAllListeners(); - this._setMainHandlers(); - this.subscribe(allAddresses); - this.subscribeToBlocks(); - } -}; - - -Insight.prototype.broadcast = function(rawtx, cb) { - preconditions.checkArgument(rawtx); - preconditions.shouldBeFunction(cb); - - this.requestPost('/api/tx/send', { - rawtx: rawtx - }, function(err, res, body) { - if (err || res.statusCode != 200) return cb(err || body); - return cb(null, body ? body.txid : null); - }); -}; - -Insight.prototype.getTransaction = function(txid, cb) { - preconditions.shouldBeFunction(cb); - this.request('/api/tx/' + txid, function(err, res, body) { - if (err || res.statusCode != 200 || !body) return cb(err || res); - cb(null, JSON.parse(body)); - }); -}; - -Insight.prototype.getTransactions = function(addresses, from, to, cb) { - preconditions.shouldBeArray(addresses); - preconditions.shouldBeFunction(cb); - - var qs = ''; - if (_.isNumber(from)) { - qs += '?from=' + from; - if (_.isNumber(to)) { - qs += '&to=' + to; - } - } - - this.requestPost('/api/addrs/txs' + qs, { - addrs: addresses.join(',') - }, function(err, res, txs) { - if (err || res.statusCode != 200) return cb(err || res); - cb(null, txs); - }); -}; - -Insight.prototype.getUnspent = function(addresses, cb) { - preconditions.shouldBeArray(addresses); - preconditions.shouldBeFunction(cb); - - this.requestPost('/api/addrs/utxo', { - addrs: addresses.join(',') - }, function(err, res, unspentRaw) { - if (err || res.statusCode != 200) return cb(err || res); - - // This filter out possible broken unspent, as reported on - // https://github.com/bitpay/copay/issues/1585 - // and later gitter conversation. - - var unspent = _.filter(unspentRaw, 'scriptPubKey'); - cb(null, unspent); - }); -}; - -Insight.prototype.getActivity = function(addresses, cb) { - preconditions.shouldBeArray(addresses); - - this.getTransactions(addresses, null, null, function then(err, txs) { - if (err) return cb(err); - - var flatArray = function(xss) { - return xss.reduce(function(r, xs) { - return r.concat(xs); - }, []); - }; - var getInputs = function(t) { - return t.vin.map(function(vin) { - return vin.addr - }); - }; - var getOutputs = function(t) { - return flatArray( - t.vout.map(function(vout) { - return vout.scriptPubKey.addresses; - }) - ); - }; - - var activityMap = new Array(addresses.length); - var activeAddress = flatArray(txs.map(function(t) { - return getInputs(t).concat(getOutputs(t)); - })); - activeAddress.forEach(function(addr) { - var index = addresses.indexOf(addr); - if (index != -1) activityMap[index] = true; - }); - - cb(null, activityMap); - }); -}; - -module.exports = Insight; diff --git a/js/models/PluginManager.js b/js/models/PluginManager.js deleted file mode 100644 index c4082ecbd..000000000 --- a/js/models/PluginManager.js +++ /dev/null @@ -1,57 +0,0 @@ -'use strict'; -var preconditions = require('preconditions').singleton(); -var log = require('../util/log'); - -function PluginManager(config) { - this.registered = {}; - this.scripts = []; - - for (var ii in config.plugins) { - var pluginName = ii; - - if (!config.plugins[pluginName]) - continue; - - log.info('Loading plugin: ' + pluginName); - var pluginClass; - if(config.pluginsPath){ - pluginClass = require(config.pluginsPath + pluginName); - } else { - pluginClass = require('../js/plugins/' + pluginName); - } - var pluginObj = new pluginClass(config[pluginName]); - pluginObj.init(); - this._register(pluginObj, pluginName); - } -}; - -var KIND_UNIQUE = PluginManager.KIND_UNIQUE = 1; -var KIND_MULTIPLE = PluginManager.KIND_MULTIPLE = 2; - -PluginManager.TYPE = {}; -PluginManager.TYPE['DB'] = KIND_UNIQUE; - -PluginManager.prototype._register = function(obj, name) { - preconditions.checkArgument(obj.type, 'Plugin has not type:' + name); - var type = obj.type; - var kind = PluginManager.TYPE[type]; - - preconditions.checkArgument(kind, 'Unknown plugin type:' + name); - preconditions.checkState(kind !== PluginManager.KIND_UNIQUE || !this.registered[type], 'Plugin kind already registered:' + name); - - if (kind === PluginManager.KIND_UNIQUE) { - this.registered[type] = obj; - } else { - this.registered[type] = this.registered[type] || []; - this.registered[type].push(obj); - } - - this.scripts = this.scripts.concat(obj.scripts || []); -}; - - -PluginManager.prototype.get = function(type) { - return this.registered[type]; -}; - -module.exports = PluginManager; diff --git a/js/models/PrivateKey.js b/js/models/PrivateKey.js deleted file mode 100644 index 9ae522814..000000000 --- a/js/models/PrivateKey.js +++ /dev/null @@ -1,264 +0,0 @@ -'use strict'; - -// 62.9% typed (by google's closure-compiler account) - -var bitcore = require('bitcore'); -var HK = bitcore.HierarchicalKey; -var WalletKey = bitcore.WalletKey; -var networks = bitcore.networks; -var util = bitcore.util; -var _ = require('lodash'); -var preconditions = require('preconditions').instance(); -var HDPath = require('./HDPath'); - -/** - * @desc - * Wrapper for bitcore.HierarchicalKey to be used inside of Copay. - * - * @param {Object} opts - * @param {string} opts.networkName if set to 'testnet', use the test3 bitcoin - * network constants (livenet otherwise) - * @param {string} opts.extendedPrivateKeyString if set, use this private key - * string, othewise create a new - * private key - * @constructor - */ -function PrivateKey(opts) { - opts = opts || {}; - this.network = opts.networkName === 'testnet' ? networks.testnet : networks.livenet; - var init = opts.extendedPrivateKeyString || this.network.name; - this.bip = new HK(init); - this.privateKeyCache = {}; - this.publicHex = this.deriveBIP45Branch().eckey.public.toString('hex'); -}; - -/** - * @desc Retrieve this derivated private key's public key in hexa format - * - * The value returned is calculated using the path from PrivateKey's - * HDParams.IdFullBranch. This key is used to identify the copayer - * (signing messages mostly). - * - * @returns {string} the public key in a hexadecimal string - */ -PrivateKey.prototype.getId = function() { - if (!this.id) { - this.cacheId(); - } - return this.id; -}; - -/** - * @desc Retrieve this private key's private key in hex format - * - * The value returned is calculated using the path from PrivateKey's - * HDParams.IdFullBranch. This key is used to identify the copayer - * (signing messages mostly). - * - * @returns {string} the private key in a hexadecimal string - */ -PrivateKey.prototype.getIdPriv = function() { - if (!this.idpriv) { - this.cacheId(); - } - return this.idpriv; -}; - -/** - * @desc Retrieve this private key's private key - * - * The value returned is calculated using the path from PrivateKey's - * HDParams.IdFullBranch. This key is used to identify the copayer - * (signing messages mostly). - * - * @returns {bitcore.PrivateKey} the private key - */ -PrivateKey.prototype.getIdKey = function() { - if (!this.idkey) { - this.cacheId(); - } - return this.idkey; -}; - -/** - * @desc Caches the result of deriving IdFullBranch - * - * @private - */ -PrivateKey.prototype.cacheId = function() { - var path = HDPath.IdFullBranch; - var idhk = this.bip.derive(path); - this.idkey = idhk.eckey; - this.id = idhk.eckey.public.toString('hex'); - this.idpriv = idhk.eckey.private.toString('hex'); -}; - -/** - * @desc Derive the master branch for Copay. - */ -PrivateKey.prototype.deriveBIP45Branch = function() { - if (!this.bip45Branch) { - this.bip45Branch = this.bip.derive(HDPath.BIP45_PUBLIC_PREFIX); - } - return this.bip45Branch; -}; - -/** - * @desc Returns an object with information needed to rebuild a PrivateKey - * (as most of its properties are derived from the extended private key). - * - * @TODO: Figure out if this is the correct pattern - * This is a static method and is probably used for serialization. - * - * @static - * @param {Object} data - * @param {*} data.networkName - a name for a bitcoin network - * @param {*} data.extendedPrivateKeyString - a bip32 extended private key - * @returns {Object} an object with two properties: networkName and - * extendedPrivateKeyString, taken from the data - * parameter. - */ -PrivateKey.trim = function(data) { - var opts = {}; - ['networkName', 'extendedPrivateKeyString'].forEach(function(k){ - opts[k] = data[k]; - }); - return opts -}; - -/** - * @desc Generate a private Key from a serialized object - * - * @TODO: This method uses PrivateKey.trim but it's actually not needed... - * - * @param {Object} data - * @param {*} data.networkName - a name for a bitcoin network - * @param {*} data.extendedPrivateKeyString - a bip32 extended private key - * @returns {PrivateKey} - */ -PrivateKey.fromObj = function(obj) { - return new PrivateKey(PrivateKey.trim(obj)); -}; - -/** - * @desc Serialize a private key, keeping only the data necessary to rebuild it - * - * @returns {Object} - */ -PrivateKey.prototype.toObj = function() { - return { - extendedPrivateKeyString: this.getExtendedPrivateKeyString(), - networkName: this.network.name - }; -}; - -/** - * @desc Retrieve a BIP32 extended public key as generated by bitcore - * - * @returns {string} - */ -PrivateKey.prototype.getExtendedPublicKeyString = function() { - return this.bip.extendedPublicKeyString(); -}; - -/** - * @desc Retrieve a BIP32 extended private key as generated by bitcore - * - * @returns {string} - */ -PrivateKey.prototype.getExtendedPrivateKeyString = function() { - return this.bip.extendedPrivateKeyString(); -}; - -/** - * @desc - * Retrieve a HierarchicalKey derived from the given path as generated by - * bitcore - * @param {string} path - a string for derivation (something like "m/234'/1/2") - * @returns {bitcore.HierarchicalKey} - */ -PrivateKey.prototype._getHK = function(path) { - if (_.isUndefined(path)) { - return this.bip; - } - var ret = this.bip.derive(path); - return ret; -}; - -/** - * @desc - * Retrieve an array of WalletKey derived from given paths. {@see PrivateKey#getForPath} - * - * @param {string[]} paths - the paths to derive - * @returns {bitcore.WalletKey[]} - the derived keys - */ -PrivateKey.prototype.getForPaths = function(paths) { - return paths.map(this.getForPath.bind(this)); -}; - -/** - * @desc - * Retrieve a WalletKey derived from a path. - * - * @param {string} paths - the path to derive - * @returns {bitcore.WalletKey} - the derived key - */ -PrivateKey.prototype.getForPath = function(path) { - var pk = this.privateKeyCache[path]; - if (!pk) { - var derivedHK = this._getHK(path); - pk = this.privateKeyCache[path] = derivedHK.eckey.private.toString('hex'); - } - var wk = new WalletKey({ - network: this.network - }); - wk.fromObj({ - priv: pk - }); - return wk; -}; - -/** - * @desc - * Retrieve a Branch for Copay using the given path - * - * @TODO: Investigate when is this called and if this is really needed - * - * @param {number} index - the index of the key to generate - * @param {boolean} isChange - whether this is a change adderess or a receive - * @param {number} cosigner - the cosigner index - * @return {bitcore.HierarchicalKey} - */ -PrivateKey.prototype.get = function(index, isChange, cosigner) { - - // TODO: Add parameter validation? - - var path = HDPath.FullBranch(index, isChange, cosigner); - return this.getForPath(path); -}; - -/** - * @desc - * Retrieve multiple branches for Copay up to the received indexes - * - * @TODO: Investigate when is this called and if this is really needed - * - * @param {number} receiveIndex - the number of receive addresses to generate - * @param {number} changeIndex - the number of change addresses to generate - * @param {number} cosigner - the cosigner index - * @return {bitcore.HierarchicalKey} - */ -PrivateKey.prototype.getAll = function(receiveIndex, changeIndex, cosigner) { - preconditions.checkArgument(!_.isUndefined(receiveIndex) && !_.isUndefined(changeIndex)); - - var ret = []; - for (var i = 0; i < receiveIndex; i++) { - ret.push(this.get(i, false, cosigner)); - } - for (var i = 0; i < changeIndex; i++) { - ret.push(this.get(i, true, cosigner)); - } - return ret; -}; - -module.exports = PrivateKey; diff --git a/js/models/PublicKeyRing.js b/js/models/PublicKeyRing.js deleted file mode 100644 index ca48d08c8..000000000 --- a/js/models/PublicKeyRing.js +++ /dev/null @@ -1,803 +0,0 @@ -'use strict'; - -var preconditions = require('preconditions').instance(); -var _ = require('lodash'); -var log = require('../util/log'); -var bitcore = require('bitcore'); -var HK = bitcore.HierarchicalKey; -var Address = bitcore.Address; -var Script = bitcore.Script; -var PrivateKey = require('./PrivateKey'); -var HDPath = require('./HDPath'); -var HDParams = require('./HDParams'); - -/** - * @desc Represents a public key ring, the set of all public keys and the used indexes - * - * @constructor - * @param {Object} opts - * @param {string} opts.walletId - * @param {string} opts.network 'livenet' to signal the bitcoin main network, all others are testnet - * @param {number=} opts.requiredCopayers - defaults to 3 - * @param {number=} opts.totalCopayers - defaults to 5 - * @param {Object[]} [opts.indexes] - an array to be deserialized using {@link HDParams#fromList} - * (defaults to all indexes in zero) - * @param {Object=} opts.nicknameFor - nicknames for other copayers - */ -function PublicKeyRing(opts) { - opts = opts || {}; - - this.walletId = opts.walletId; - - this.network = opts.networkName === 'livenet' ? - bitcore.networks.livenet : bitcore.networks.testnet; - - this.requiredCopayers = opts.requiredCopayers || 3; - this.totalCopayers = opts.totalCopayers || 5; - - this.copayersHK = []; - - this.indexes = opts.indexes ? HDParams.fromList(opts.indexes) : HDParams.init(this.totalCopayers); - - this.publicKeysCache = {}; - this.nicknameFor = opts.nicknameFor || {}; - this.copayerIds = []; - - this.resetCache(); -}; - -PublicKeyRing.prototype.resetCache = function() { - this.cache = {}; - this.cache.addressToPath = {}; - this.cache.pathToAddress = {}; - - // Non persistent cache - this._isChange = {}; -}; - - -/** - * @desc Returns an object with only the keys needed to rebuild a PublicKeyRing - * - * @TODO: Figure out if this is the correct pattern - * This is a static method and is probably used for serialization. - * - * @static - * @param {Object} data - * @param {string} data.walletId - a string to identify a wallet - * @param {string} data.networkName - the name of the bitcoin network - * @param {number} data.requiredCopayers - the number of required copayers - * @param {number} data.totalCopayers - the number of copayers in the ring - * @param {Object[]} data.indexes - an array of objects that can be turned into - * an array of HDParams - * @param {Object} data.nicknameFor - a registry of nicknames for other copayers - * @param {string[]} data.copayersExtPubKeys - the extended public keys of copayers - * @returns {Object} a trimmed down version of PublicKeyRing that can be used - * as a parameter - */ -PublicKeyRing.trim = function(data) { - preconditions.checkArgument(data); - var opts = {}; - ['walletId', 'networkName', 'requiredCopayers', 'totalCopayers', - 'indexes', 'nicknameFor', 'copayersExtPubKeys' - ].forEach(function(k) { - opts[k] = data[k]; - }); - return opts; -}; - -/** - * @desc Deserializes a PublicKeyRing from a plain object - * - * If the data parameter is an instance of PublicKeyRing already, - * it will fail, throwing an assertion error. - * - * @static - * @param {object} data - a serialized version of PublicKeyRing {@see PublicKeyRing#trim} - * @return {PublicKeyRing} - the deserialized object - */ -PublicKeyRing.fromObj = function(opts) { - preconditions.checkArgument(!(opts instanceof PublicKeyRing), 'bad opts format: Did you use .toObj()?'); - - // Support old indexes schema - if (!Array.isArray(opts.indexes)) { - opts.indexes = HDParams.update(opts.indexes, opts.totalCopayers); - } - - var pkr = new PublicKeyRing(opts); - - for (var k in opts.copayersExtPubKeys) { - pkr.addCopayer(opts.copayersExtPubKeys[k]); - } - - if (opts.cache && opts.cache.addressToPath) { - log.debug('PublicKeyRing: Using address cache'); - pkr.cache.addressToPath = opts.cache.addressToPath; - pkr.rebuildCache(); - } - - return pkr; -}; - - -PublicKeyRing.prototype.rebuildCache = function() { - if (!this.cache.addressToPath) - return; - - var self = this; - _.each(this.cache.addressToPath, function(path, address) { - self.cache.pathToAddress[path] = address; - }); -}; - -PublicKeyRing.fromUntrustedObj = function(opts) { - return PublicKeyRing.fromObj(PublicKeyRing.trim(opts)); -}; - -/** - * @desc Serialize this object to a plain object with all the data needed to - * rebuild it - * - * @return {Object} a serialized version of a PublicKeyRing - */ -PublicKeyRing.prototype.toObj = function() { - return { - walletId: this.walletId, - networkName: this.network.name, - requiredCopayers: this.requiredCopayers, - totalCopayers: this.totalCopayers, - indexes: HDParams.serialize(this.indexes), - - copayersExtPubKeys: this.copayersHK.map(function(b) { - return b.extendedPublicKeyString(); - }), - nicknameFor: this.nicknameFor, - - // We only store addressToPath and derive the reset from it - cache: { - addressToPath: this.cache.addressToPath - }, - }; -}; - - -PublicKeyRing.prototype.toTrimmedObj = function() { - return PublicKeyRing.trim(this.toObj()); -} - - -/** - * @desc - * Retrieve a copayer's public key as a hexadecimal encoded string - * - * @param {number} copayerId - the copayer id - * @returns {string} the extended public key of the i-th copayer - */ -PublicKeyRing.prototype.getCopayerId = function(copayerId) { - preconditions.checkArgument(!_.isUndefined(copayerId)) - preconditions.checkArgument(_.isNumber(copayerId)); - - return this.copayerIds[copayerId]; -}; - -/** - * @desc - * Get the amount of registered copayers in this PubKeyRing - * - * @returns {number} amount of copayers present - */ -PublicKeyRing.prototype.registeredCopayers = function() { - return this.copayersHK.length; -}; - -/** - * @desc - * Returns true if all the needed copayers have joined the public key ring - * - * @returns {boolean} - */ -PublicKeyRing.prototype.isComplete = function() { - return this.remainingCopayers() == 0; -}; - -/** - * @desc - * Returns the number of copayers yet to join to make the public key ring complete - * - * @returns {number} - */ -PublicKeyRing.prototype.remainingCopayers = function() { - return this.totalCopayers - this.registeredCopayers(); -}; - -/** - * @desc - * Returns an array of copayer's public keys - * - * @returns {string[]} a list of hexadecimal strings with the public keys for - * the copayers in this ring - */ -PublicKeyRing.prototype.getAllCopayerIds = function() { - return this.copayerIds; -}; - -/** - * @desc - * Gets the current user's copayerId - * - * @returns {string} the extended public key hexadecimal-encoded - */ -PublicKeyRing.prototype.myCopayerId = function() { - return this.getCopayerId(0); -}; - -/** - * @desc Throws an error if the public key ring isn't complete - */ -PublicKeyRing.prototype._checkKeys = function() { - if (!this.isComplete()) throw new Error('dont have required keys yet'); -}; - -/** - * @desc - * Updates the internal register of the public hex string for a copayer, based - * on the value of the hierarchical key stored in copayersHK - * - * @private - * @param {number} index - the index of the copayer to update - */ -PublicKeyRing.prototype._updateBip = function(index) { - var hk = this.copayersHK[index].derive(HDPath.IdBranch); - this.copayerIds[index] = hk.eckey.public.toString('hex'); -}; - -/** - * @desc - * Sets a nickname for one of the copayers - * - * @private - * @param {number} index - the index of the copayer to update - * @param {string} nickname - the new nickname for that copayer - */ -PublicKeyRing.prototype._setNicknameForIndex = function(index, nickname) { - this.nicknameFor[this.copayerIds[index]] = nickname; -}; - -/** - * @desc - * Fetch the name of a copayer - * - * @param {number} index - the index of the copayer - * @return {string} the nickname of the index-th copayer - */ -PublicKeyRing.prototype.nicknameForIndex = function(index) { - return this.nicknameFor[this.copayerIds[index]]; -}; - -/** - * @desc - * Fetch the name of a copayer using its public key - * - * @param {string} copayerId - the public key ring of a copayer, hex encoded - * @return {string} the nickname of the copayer with such pubkey - */ -PublicKeyRing.prototype.nicknameForCopayer = function(copayerId) { - return this.nicknameFor[copayerId] || 'NN'; -}; - -/** - * @desc - * Add a copayer into the public key ring. - * - * @param {string} newHexaExtendedPublicKey - an hex encoded string with the copayer's pubkey - * @param {string} nickname - a nickname for this copayer - * @return {string} the newHexaExtendedPublicKey parameter - */ -PublicKeyRing.prototype.addCopayer = function(newHexaExtendedPublicKey, nickname) { - preconditions.checkArgument(newHexaExtendedPublicKey && _.isString(newHexaExtendedPublicKey)); - preconditions.checkArgument(!this.isComplete()); - preconditions.checkArgument(!nickname || _.isString(nickname)); - preconditions.checkArgument(!_.any(this.copayersHK, - function(copayer) { - return copayer.extendedPublicKeyString === newHexaExtendedPublicKey; - } - )); - - var newCopayerIndex = this.copayersHK.length; - var hierarchicalKey = new HK(newHexaExtendedPublicKey); - - this.copayersHK.push(hierarchicalKey); - this._updateBip(newCopayerIndex); - - if (nickname) { - this._setNicknameForIndex(newCopayerIndex, nickname); - } - return newHexaExtendedPublicKey; -}; - -/** - * @desc - * Get all the public keys for the copayers in this ring, for a given branch of Copay - * - * @param {number} index - the index for the shared address - * @param {boolean} isChange - whether to derive a change address o receive address - * @param {number} copayerIndex - the index of the copayer that requested the derivation - * @return {Buffer[]} an array of derived public keys in hexa format - */ -PublicKeyRing.prototype.getPubKeys = function(index, isChange, copayerIndex) { - this._checkKeys(); - - log.warn('Slow pubkey derivation...'); - var path = HDPath.Branch(index, isChange, copayerIndex); - var pubKeys = _.map(this.copayersHK, function(hdKey) { - return hdKey.derive(path).eckey.public; - }); - - return pubKeys; -}; - -/** - * @desc - * Generate a new Script for a copay address generated by index, isChange, and copayerIndex - * - * @TODO this could be cached - * - * @param {number} index - the index for the shared address - * @param {boolean} isChange - whether to derive a change address o receive address - * @param {number} copayerIndex - the index of the copayer that requested the derivation - * @returns {bitcore.Script} - */ -PublicKeyRing.prototype.getRedeemScript = function(index, isChange, copayerIndex) { - var pubKeys = this.getPubKeys(index, isChange, copayerIndex); - var script = Script.createMultisig(this.requiredCopayers, pubKeys); - return script; -}; - - - -/** - * @desc - * Get the address for a multisig based on the given params. - * - * Caches the address to the branch in the member addressToPath - * - * @param {number} index - the index for the shared address - * @param {boolean} isChange - whether to derive a change address o receive address - * @param {number} copayerIndex - the index of the copayer that requested the derivation - * @returns {bitcore.Address} - */ -PublicKeyRing.prototype._getAddress = function(index, isChange, id) { - var copayerIndex = this.getCosigner(id); - var path = HDPath.FullBranch(index, isChange, copayerIndex); - if (this.cache.pathToAddress[path]) - return this.cache.pathToAddress[path]; - - log.info('Generating Address:', index, isChange, copayerIndex); - var script = this.getRedeemScript(index, isChange, copayerIndex); - var address = Address.fromScript(script, this.network.name).toString(); - - this._cacheAddress(address, path, isChange); - return address; -}; - -PublicKeyRing.prototype._cacheAddress = function(address, path, isChange) { - this.cache.addressToPath[address] = path; - this.cache.pathToAddress[path] = address; -}; - -/** - * @desc - * Get the parameters used to derive a pubkey or a cosigner index - * - * Overloaded to receive a PubkeyString or a consigner index - * - * @param {number|string} id public key in hex format, or the copayer's index - * @return ???? - */ -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]; -}; - -/** - * @desc - * Get the path used to derive a pubkey or a cosigner index for an address - * - * @param {string} address a multisig p2sh address - * @return {HDPath} - */ -PublicKeyRing.prototype.pathForAddress = function(address) { - this._checkCache(); - var path = this.cache.addressToPath[address]; - if (!path) throw new Error('Couldn\'t find path for address ' + address); - return path; -}; - -/** - * @desc - * Generates a new address and updates the last index used - * - * @param {truthy} isChange - generate a change address if true, otherwise - * generates a receive - * @param {number|string} pubkey - the pubkey for the copayer that generates the - * address (or index in the keyring) - * @returns {bitpay.Address} - */ -PublicKeyRing.prototype.generateAddress = function(isChange, pubkey) { - isChange = !!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; -}; - -/** - * @desc Is an address is from this wallet? - * - * @param {string} address - * @return {boolean} - */ -PublicKeyRing.prototype.addressIsOwn = function(address) { - return !!this.cache.addressToPath[address]; -}; - -/** - * @desc Is an address is a change address? - * - * @param {string} address - * @return {boolean} - */ -PublicKeyRing.prototype.addressIsChange = function(address) { - this._checkCache(); - - var path = this.cache.addressToPath[address]; - if (!path) - return null; - - var p = HDPath.indexesForPath(path); - - //Memoization Only, never stored. - this._isChange[address] = p.isChange; - return !!this._isChange[address]; -}; - - -/** - * @desc - * Maps a copayer's public key to his index in the keyring - * - * @param {number|string|undefined} pubKey - if undefined, returns the SHARED_INDEX - * - if a number, just return it - * - if a string, assume is the hex encoded public key - * @returns {number} the index of the copayer with the given pubkey - */ -PublicKeyRing.prototype.getCosigner = function(pubKey) { - if (_.isUndefined(pubKey)) return HDPath.SHARED_INDEX; - if (_.isNumber(pubKey)) return pubKey; - - var sorted = this.copayersHK.map(function(h, i) { - return h.eckey.public.toString('hex'); - }).sort(function(h1, h2) { - return h1.localeCompare(h2); - }); - - var index = sorted.indexOf(pubKey); - if (index == -1) throw new Error('public key is not on the ring'); - - return index; -}; - - - -PublicKeyRing.prototype.buildAddressCache = function() { - var ret = []; - var self = this; - - _.each(this.indexes, function(index) { - for (var i = 0; i < index.receiveIndex; i++) { - self._getAddress(i, false, index.copayerIndex); - } - for (var i = 0; i < index.changeIndex; i++) { - self._getAddress(i, true, index.copayerIndex); - } - }); -}; - - -PublicKeyRing.prototype.size = function(opts) { - var self = this; - return _.reduce(this.indexes, function(sum, index) { - return sum + index.receiveIndex + index.changeIndex - }, 0); -}; - -PublicKeyRing.prototype._checkCache = function(opts) { - if (_.isEmpty(this.cache.addressToPath)) { - this.buildAddressCache(); - } - if (_.size(this.cache.addressToPath) !== this.size()) { - this.buildAddressCache(); - } -}; - - -/** - * @desc - * Gets information about addresses for a copayer - * - * @param {Object} opts - * @returns {AddressInfo[]} - */ -PublicKeyRing.prototype.getAddresses = function() { - this._checkCache(); - return _.keys(this.cache.addressToPath); -}; - -/** - * getAddressesOrdered - * {@link Wallet#getAddressesOrdered} - * - * @param pubkey - * @return {string[]} - */ -PublicKeyRing.prototype.getAddressesOrdered = function(pubkey) { - this._checkCache(); - - var info = _.map(this.cache.addressToPath, function(path, addr) { - var p = HDPath.indexesForPath(path); - p.address = addr; - return p; - }); - - var copayerIndex = this.getCosigner(pubkey); - var l = info.length; - - var sortedInfo = _.sortBy(info, function(i) { - var goodness = ((i.copayerIndex !== copayerIndex) ? 2 * l : 0) + (i.isChange ? l : 0) + l - i.addressIndex; - return goodness; - }); - - return _.pluck(sortedInfo, 'address'); -}; - - - -/** - * @desc - * Gets information about addresses for a copayer - * - * @param {Object} opts - * @returns {AddressInfo[]} - */ -PublicKeyRing.prototype.getReceiveAddresses = function() { - this._checkCache(); - - var self = this; - return _.filter(this.getAddresses(), function(addr) { - return !self.addressIsChange(addr); - }); -}; - - - -/** - * @desc - * Retrieve the public keys for all cosigners for a given path - * - * @param {string} path - the BIP32 path - * @return {Buffer[]} the public keys, in buffer format - */ -PublicKeyRing.prototype._getForPath = function(path) { - var p = HDPath.indexesForPath(path); - return this.getPubKeys(p.addressIndex, p.isChange, p.copayerIndex); -}; - - -/** - * @desc - * Retrieve the public keys for derived addresses and the public keys for copayers - * - * @TODO: Should this exist? A user should just call _getForPath(paths) - * - * @param {string[]} paths - the paths to be derived - * @return {Object} with keys pubKeys and copayerIds - */ -PublicKeyRing.prototype.forPaths = function(paths) { - return { - pubKeys: paths.map(this._getForPath.bind(this)), - copayerIds: this.copayerIds, - } -}; - -/** - * @desc - * Retrieve the public keys for all cosigners for multiple paths - * - * @param {string[]} paths - the BIP32 paths - * @return {Array[]} the public keys, in buffer format (matrix of Buffer, Buffer[][]) - */ -PublicKeyRing.prototype._getForPaths = function(paths) { - preconditions.checkArgument(!_.isUndefined(paths)); - preconditions.checkArgument(_.isArray(paths)); - preconditions.checkArgument(_.all(paths, _.isString)); - - return paths.map(this._getForPath.bind(this)); -}; - -/** - * @desc - * Returns a map from a pubkey of an address to the id that generated it - * - * @param {string[]} pubkeys - the pubkeys to query - * @param {string[]} paths - the paths to query - */ -PublicKeyRing.prototype.copayersForPubkeys = function(pubkeys, paths) { - preconditions.checkArgument(pubkeys); - preconditions.checkArgument(paths); - - var inKeyMap = {}, - ret = {}; - for (var i in pubkeys) { - inKeyMap[pubkeys[i]] = 1; - }; - - var keys = this._getForPaths(paths); - for (var i in keys) { - for (var copayerIndex in keys[i]) { - var kHex = keys[i][copayerIndex].toString('hex'); - if (inKeyMap[kHex]) { - ret[kHex] = this.copayerIds[copayerIndex]; - delete inKeyMap[kHex]; - } - } - } - - if (_.size(inKeyMap)) { - for (var i in inKeyMap) { - log.error('Pubkey ' + i + ' not identified'); - } - throw new Error('Pubkeys not identified'); - } - - return ret; -}; - -/** - * @desc - * Returns a map from address -> public key needed - * - * @param {HDPath[]} paths - paths to be solved - * @returns {Object} a map from addresses to Buffer with the hex pubkeys - */ -PublicKeyRing.prototype.getRedeemScriptMap = function(paths) { - var ret = {}; - for (var i = 0; i < paths.length; i++) { - var path = paths[i]; - var p = HDPath.indexesForPath(path); - var script = this.getRedeemScript(p.addressIndex, p.isChange, p.copayerIndex); - ret[Address.fromScript(script, this.network.name).toString()] = script.getBuffer().toString('hex'); - } - return ret; -}; - -/** - * @desc - * Check if another PubKeyRing is similar to this one (checks network name, - * requiredCopayers, and totalCopayers). If ignoreId is falsy, also check that - * both walletIds match. - * - * @private - * @param {PubKeyRing} inPKR - the other PubKeyRing - * @param {boolean} ignoreId - whether to ignore checking for equal walletId - * @throws {Error} if the wallets mismatch - * @return true - */ - -PublicKeyRing.prototype._checkInPKR = function(inPKR, ignoreId) { - preconditions.checkArgument(_.isObject(inPKR)); - - if (!ignoreId && this.walletId !== inPKR.walletId) - throw new Error('inPKR walletId mismatch'); - - if (this.network.name !== inPKR.network.name) - throw new Error('Network mismatch. Should be ' + this.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); - - if (this.totalCopayers && inPKR.totalCopayers && - this.totalCopayers !== inPKR.totalCopayers) - throw new Error('inPKR totalCopayers mismatch' + this.totalCopayers + - '!=' + inPKR.requiredCopayers); - - return true; -}; - -/** - * @desc - * Merges the public keys of the wallet passed in as a parameter with ours. - * - * @param {PublicKeyRing} inPKR - * @return {boolean} true if there where changes in our internal state - */ -PublicKeyRing.prototype._mergePubkeys = function(inPKR) { - var self = this; - var hasChanged = false; - - if (self.isComplete()) - return; - - inPKR.copayersHK.forEach(function(b) { - var epk = b.extendedPublicKeyString(); - var haveIt = _.any(self.copayersHK, function(hk) { - return hk.extendedPublicKeyString() === epk; - }); - - if (!haveIt) { - if (self.isComplete()) { - throw new Error('trying to add more pubkeys, when PKR isComplete at merge'); - } - var l2 = self.copayersHK.length; - self.copayersHK.push(new HK(epk)); - self._updateBip(l2); - if (inPKR.nicknameFor[self.getCopayerId(l2)]) - self._setNicknameForIndex(l2, inPKR.nicknameFor[self.getCopayerId(l2)]); - hasChanged = true; - } - }); - return hasChanged; -}; - - -/** - * @desc - * Merges the indexes for addresses generated with another copy of a list of - * HDParams - * - * @param {HDParams[]} indexes - indexes as received from another sources - * @return {boolean} true if the internal state has changed - */ -PublicKeyRing.prototype.mergeIndexes = function(indexes) { - var self = this; - var hasChanged = false; - - indexes.forEach(function(theirs) { - var mine = self.getHDParams(theirs.copayerIndex); - hasChanged |= mine.merge(theirs); - }); - - return !!hasChanged -} - - -/** - * @desc - * Merges this public key ring with another one, optionally ignoring the - * wallet id - * - * @param {PublicKeyRing} inPkr - * @param {boolean} ignoreId - * @return {boolean} true if the internal state has changed - */ -PublicKeyRing.prototype.merge = function(inPKR, ignoreId) { - - this._checkInPKR(inPKR, ignoreId); - var hasChanged = false; - hasChanged |= this.mergeIndexes(inPKR.indexes); - hasChanged |= this._mergePubkeys(inPKR); - - return !!hasChanged; -}; - - - -module.exports = PublicKeyRing; diff --git a/js/models/TxProposal.js b/js/models/TxProposal.js deleted file mode 100644 index ba3def705..000000000 --- a/js/models/TxProposal.js +++ /dev/null @@ -1,624 +0,0 @@ -'use strict'; - -var _ = require('lodash'); -var preconditions = require('preconditions').singleton(); - -var bitcore = require('bitcore'); -var util = bitcore.util; -var Transaction = bitcore.Transaction; -var TransactionBuilder = bitcore.TransactionBuilder; -var Script = bitcore.Script; -var Key = bitcore.Key; - -var log = require('../util/log'); - -var TX_MAX_SIZE_KB = 50; -var VERSION = 1; -var CORE_FIELDS = ['builderObj', 'inputChainPaths', 'version', 'comment', 'paymentProtocolURL', 'paymentAckMemo']; - - -function TxProposal(opts) { - preconditions.checkArgument(opts); - preconditions.checkArgument(opts.inputChainPaths, 'no inputChainPaths'); - preconditions.checkArgument(opts.builder, 'no builder'); - preconditions.checkArgument(opts.inputChainPaths, 'no inputChainPaths'); - - this.inputChainPaths = opts.inputChainPaths; - this.version = opts.version; - this.builder = opts.builder; - this.createdTs = opts.createdTs; - - // Copayer Actions ( copayerId: timeStamp ) - this.signedBy = opts.signedBy || {}; - this.seenBy = opts.seenBy || {}; - this.rejectedBy = opts.rejectedBy || {}; - - this.sentTs = opts.sentTs || null; - this.sentTxid = opts.sentTxid || null; - this.comment = opts.comment || null; - this.readonly = opts.readonly || null; - this.merchant = opts.merchant || null; - this.paymentAckMemo = opts.paymentAckMemo || null; - this.paymentProtocolURL = opts.paymentProtocolURL || null; - - this.resetCache(); - - // New Tx Proposal - if (_.isEmpty(this.seenBy) && opts.creator) { - var now = Date.now(); - var me = {}; - me[opts.creator] = now; - - this.seenBy = me; - this.signedBy = {}; - this.creator = opts.creator; - this.createdTs = now; - if (opts.signWith) { - if (!this.sign(opts.signWith, opts.creator)) - throw new Error('Could not sign generated tx'); - } - } -} - -TxProposal.prototype._checkPayPro = function() { - if (!this.merchant) return; - - if (this.paymentProtocolURL !== this.merchant.request_url) - throw new Error('PayPro: Mismatch on Payment URLs'); - - if (!this.merchant.outs || this.merchant.outs.length !== 1) - throw new Error('PayPro: Unsopported number of outputs'); - - if (this.merchant.expires < (this.getSent() || Date.now() / 1000.)) - throw new Error('PayPro: Request expired'); - - if (!this.merchant.total || !this.merchant.outs[0].amountSatStr || !this.merchant.outs[0].address) - throw new Error('PayPro: Missing amount'); - - var outs = JSON.parse(this.builder.vanilla.outs); - if (_.size(outs) != 1) - throw new Error('PayPro: Wrong outs in Tx'); - - var ppOut = this.merchant.outs[0]; - var txOut = outs[0]; - - if (ppOut.address !== txOut.address) - throw new Error('PayPro: Wrong out address in Tx'); - - if (ppOut.amountSatStr !== txOut.amountSatStr + '') - throw new Error('PayPro: Wrong amount in Tx'); - -}; - - -TxProposal.prototype.isFullySigned = function() { - return this.builder && this.builder.isFullySigned(); -}; - - -TxProposal.prototype.getMySignatures = function() { - preconditions.checkState(this._mySignatures, 'Still no signatures from us'); - return _.clone(this._mySignatures); -}; - -TxProposal.prototype._setMySignatures = function(signaturesBefore) { - var mySigs = []; - _.each(this.getSignatures(), function(signatures, index) { - var diff = _.difference(signatures, signaturesBefore[index]); - preconditions.checkState(diff.length == 1, 'more that one signature added!'); - mySigs.push(diff[0].toString('hex')); - }) - this._mySignatures = mySigs; - return; -}; - -TxProposal.prototype.sign = function(keys, signerId) { - var before = this.countSignatures(); - var signaturesBefore = this.getSignatures(); - this.builder.sign(keys); - - var signaturesAdded = this.countSignatures() > before; - if (signaturesAdded) { - this.signedBy[signerId] = Date.now(); - this._setMySignatures(signaturesBefore); - } - return signaturesAdded; -}; - -TxProposal.prototype._check = function() { - - if (this.builder.signhash && this.builder.signhash !== Transaction.SIGHASH_ALL) { - throw new Error('Invalid tx proposal'); - } - - // Should be able to build - var tx = this.builder.build(); - - var txSize = tx.getSize(); - if (txSize / 1024 > TX_MAX_SIZE_KB) - throw new Error('BIG: Invalid TX proposal. Too big: ' + txSize + ' bytes'); - - if (!tx.ins.length) - throw new Error('Invalid tx proposal: no ins'); - - _.each(tx.ins, function(value, index) { - var scriptSig = value.s; - if (!scriptSig || !scriptSig.length) { - throw new Error('Invalid tx proposal: no signatures'); - } - var hashType = tx.getHashType(index); - if (hashType && hashType !== Transaction.SIGHASH_ALL) - throw new Error('Invalid tx proposal: bad signatures'); - }); - this._checkPayPro(); -}; - - -TxProposal.prototype.addMerchantData = function(merchantData) { - preconditions.checkArgument(merchantData.pr); - preconditions.checkArgument(merchantData.request_url); - var m = _.clone(merchantData); - - if (!this.paymentProtocolURL) - this.paymentProtocolURL = m.request_url; - - // remove unneeded data - m.raw = m.pr.pki_data = m.pr.signature = undefined; - this.merchant = m; - this._checkPayPro(); -}; - -TxProposal.prototype.getSignatures = function() { - var ins = this.builder.build().ins; - var sigs = _.map(ins, function(value) { - var script = new bitcore.Script(value.s); - var nchunks = script.chunks.length; - return _.map(script.chunks.slice(1, nchunks - 1), function(buffer) { - return buffer.toString('hex'); - }); - }); - - return sigs; -}; - -TxProposal.prototype.rejectCount = function() { - return _.size(this.rejectedBy); -}; - - -TxProposal.prototype.isFinallyRejected = function(maxRejectCount) { - return this.rejectCount() > maxRejectCount; -}; - -TxProposal.prototype.isPending = function(maxRejectCount) { - preconditions.checkArgument(_.isNumber(maxRejectCount)); - - if (this.isFinallyRejected(maxRejectCount) || this.sentTxid) - return false; - - return true; -}; - -TxProposal.prototype._setSigned = function(copayerId) { - - // Sign powns rejected - if (this.rejectedBy[copayerId]) { - log.info("WARN: a previously rejected transaction was signed by:", copayerId); - delete this.rejectedBy[copayerId]; - } - - this.signedBy[copayerId] = Date.now(); - - return this; -}; - - - -/** - * - * @desc verify signatures of ONE copayer, using an array of signatures for each input - * - * @param {string[]} signatures, of the same copayer, one for each input - * @return {string[]} array for signing pubkeys for each input - */ -TxProposal.prototype._addSignatureAndVerify = function(signatures) { - var self = this; - - var ret = []; - var tx = self.builder.build(); - - var inputsFullySigned = 0; - var newScriptSigs = []; - - this.resetCache(); - _.each(tx.ins, function(input, index) { - var scriptSig = new Script(input.s); - - var info = TxProposal.infoFromRedeemScript(scriptSig); - var txSigHash = tx.hashForSignature(info.script, parseInt(index), Transaction.SIGHASH_ALL); - var keys = TxProposal.formatKeys(info.keys); - var sig = new Buffer(signatures[index], 'hex'); - - var hashType = sig[sig.length - 1]; - if (hashType !== Transaction.SIGHASH_ALL) - throw new Error('BADSIG: Invalid signature: Bad hash type'); - - var sigRaw = new Buffer(sig.slice(0, sig.length - 1)); - var signingPubKeyHex = self._verifyOneSignature(keys, sigRaw, txSigHash); - if (!signingPubKeyHex) - throw new Error('BADSIG: Invalid signatures: invalid for input:' + index); - - // now insert it - var keysHex = _.pluck(keys, 'keyHex'); - var prio = _.indexOf(keysHex, signingPubKeyHex); - preconditions.checkState(prio >= 0); - - var currentKeys = self.getSignersPubKeys()[index]; - - if (_.indexOf(currentKeys, signingPubKeyHex) >= 0) - throw new Error('BADSIG: Already have this signature'); - - var currentPrios = _.map(currentKeys, function(key) { - var prio = _.indexOf(keysHex, key); - preconditions.checkState(prio >= 0); - return prio; - }); - - var insertAt = 0; - while (!_.isUndefined(currentPrios[insertAt]) && prio > currentPrios[insertAt]) - insertAt++; - - // Insert it! (1 is OP_0!) - scriptSig.chunks.splice(1 + insertAt, 0, sig); - scriptSig.updateBuffer(); - - if (info.nreq == currentKeys.length + 1) { - inputsFullySigned++; - } - newScriptSigs.push(scriptSig.buffer); - }); - preconditions.checkState(newScriptSigs.length === tx.ins.length); - - // If we reach here, all signatures are OK, let's update the TX. - _.each(tx.ins, function(input, index) { - input.s = newScriptSigs[index]; - - // just to keep TransactionBuilder updated - if (tx.ins.length == inputsFullySigned) - self.builder.inputsSigned++; - }); -}; - -TxProposal.prototype.resetCache = function() { - this.cache = { - pubkeysForScript: {}, - }; -}; - -/** - * addSignature - * - * @param {string[]} signatures from *ONE* copayer, one signature for each TX input. - * @return {boolean} true = signatures added - */ -TxProposal.prototype.addSignature = function(copayerId, signatures) { - preconditions.checkArgument(_.isArray(signatures)); - - if (this.isFullySigned()) - return false; - - var tx = this.builder.build(); - preconditions.checkArgument(signatures.length === tx.ins.length, 'Wrong number of signatures given'); - - this._addSignatureAndVerify(signatures); - - this._setSigned(copayerId); - return true; -}; - -/** - * - * getSignersPubKey - * @desc get Pubkeys of signers, for each input. this is CPU intensive - * - * @return {string[][]} array of hashes for signing pubkeys for each input - */ -TxProposal.prototype.getSignersPubKeys = function(forceUpdate) { - var self = this; - - - var signersPubKey = []; - - if (!self.cache.signersPubKey || forceUpdate) { - - log.debug('PERFORMANCE WARN: Verifying *all* TX signatures:', self.getId()); - - var tx = self.builder.build(); - _.each(tx.ins, function(input, index) { - - if (!self.cache.pubkeysForScript[input.s]) { - var scriptSig = new Script(input.s); - var signatureCount = scriptSig.countSignatures(); - - var info = TxProposal.infoFromRedeemScript(scriptSig); - var txSigHash = tx.hashForSignature(info.script, parseInt(index), Transaction.SIGHASH_ALL); - var inputSignersPubKey = self.verifySignatures(info.keys, scriptSig, txSigHash); - - // Does scriptSig has strings that are not signatures? - if (inputSignersPubKey.length !== signatureCount) - throw new Error('Invalid signature'); - - self.cache.pubkeysForScript[input.s] = inputSignersPubKey; - } - - signersPubKey[index] = self.cache.pubkeysForScript[input.s]; - }); - self.cache.signersPubKey = signersPubKey; - } else { - log.debug('Using signatures verification cache') - } - - return self.cache.signersPubKey; -}; - -TxProposal.prototype.getId = function() { - preconditions.checkState(this.builder); - - if (!this.ntxid) { - this.ntxid = this.builder.build().getNormalizedHash().toString('hex'); - } - return this.ntxid; -}; - -TxProposal.prototype.toObj = function() { - var o = JSON.parse(JSON.stringify(this)); - delete o['builder']; - delete o['cache']; - o.builderObj = this.builder.toObj(); - return o; -}; - - -TxProposal._trim = function(o) { - var ret = {}; - CORE_FIELDS.forEach(function(k) { - ret[k] = o[k]; - }); - return ret; -}; - -TxProposal.fromObj = function(o, forceOpts) { - preconditions.checkArgument(o.builderObj); - delete o['builder']; - forceOpts = forceOpts || {}; - o.builderObj.opts = o.builderObj.opts || {}; - - // force opts is requested. - _.each(forceOpts, function(value, key) { - o.builderObj.opts[key] = value; - }); - - // Handle undef fee options - if (_.isUndefined(forceOpts.fee) && _.isUndefined(forceOpts.feeSat)) { - o.builderObj.opts.fee = undefined; - o.builderObj.opts.feeSat = undefined; - } - - try { - o.builder = TransactionBuilder.fromObj(o.builderObj); - } catch (e) { - throw new Error(e); - return null; - } - return new TxProposal(o); -}; - -TxProposal.fromUntrustedObj = function(o, forceOpts) { - var trimmed = TxProposal._trim(o); - var txp = TxProposal.fromObj(trimmed, forceOpts); - if (!txp) - throw new Error('Invalid Transaction'); - - txp._check(); - return txp; -}; - -TxProposal.prototype.toObjTrim = function() { - return TxProposal._trim(this.toObj()); -}; - -TxProposal.formatKeys = function(keys) { - var ret = []; - for (var i in keys) { - if (!Buffer.isBuffer(keys[i])) - throw new Error('keys must be buffers'); - - var k = new Key(); - k.public = keys[i]; - ret.push({ - keyObj: k, - keyHex: keys[i].toString('hex'), - }); - } - return ret; -}; - - -/** - * @desc Verify a single signature, for a given hash, tested against a given list of public keys. - * @param keys - * @param sigRaw - * @param txSigHash - * @return {string?} on valid signature, return the signing public key hex representation - */ -TxProposal.prototype._verifyOneSignature = function(keys, sigRaw, txSigHash) { - preconditions.checkArgument(Buffer.isBuffer(txSigHash)); - preconditions.checkArgument(Buffer.isBuffer(sigRaw)); - preconditions.checkArgument(_.isArray(keys)); - preconditions.checkArgument(keys[0].keyObj); - - var signingKey = _.find(keys, function(key) { - var ret = false; - try { - ret = key.keyObj.verifySignatureSync(txSigHash, sigRaw); - } catch (e) {}; - return ret; - }); - - return signingKey ? signingKey.keyHex : null; -}; - - -/** - * @desc verify transaction signatures - * - * @param inKeys - * @param scriptSig - * @param txSigHash - * @return {string[]} signing pubkeys, in order of apperance - */ -TxProposal.prototype.verifySignatures = function(inKeys, scriptSig, txSigHash) { - preconditions.checkArgument(Buffer.isBuffer(txSigHash)); - preconditions.checkArgument(inKeys); - preconditions.checkState(Buffer.isBuffer(inKeys[0])); - var self = this; - - if (scriptSig.chunks[0] !== 0) - throw new Error('Invalid scriptSig'); - - var keys = TxProposal.formatKeys(inKeys); - var ret = []; - for (var i = 1; i <= scriptSig.countSignatures(); i++) { - var chunk = scriptSig.chunks[i]; - log.debug('\t Verifying CHUNK:', i); - var sigRaw = new Buffer(chunk.slice(0, chunk.length - 1)); - - var signingPubKeyHex = self._verifyOneSignature(keys, sigRaw, txSigHash); - if (!signingPubKeyHex) - throw new Error('Found a signature that is invalid'); - - ret.push(signingPubKeyHex); - } - return ret; -}; - -TxProposal.infoFromRedeemScript = function(s) { - var redeemScript = new Script(s.chunks[s.chunks.length - 1]); - if (!redeemScript) - throw new Error('Bad scriptSig (no redeemscript)'); - - var nreq = nreq = redeemScript.chunks[0] - 80; //see OP_2-OP_16 - var pubkeys = redeemScript.capture(); - if (!pubkeys || !pubkeys.length) - throw new Error('Bad scriptSig (no pubkeys)'); - - return { - nreq: nreq, - keys: pubkeys, - script: redeemScript, - }; -}; - -TxProposal.prototype.getSeen = function(copayerId) { - return this.seenBy[copayerId]; -}; - -TxProposal.prototype.setSeen = function(copayerId) { - if (!this.seenBy[copayerId]) - this.seenBy[copayerId] = Date.now(); -}; - -TxProposal.prototype.setRejected = function(copayerId) { - - if (this.signedBy[copayerId]) - throw new Error('Can not reject a signed TX'); - - if (!this.rejectedBy[copayerId]) - this.rejectedBy[copayerId] = Date.now(); - - return this; -}; - -TxProposal.prototype.setSent = function(sentTxid) { - this.sentTxid = sentTxid; - this.sentTs = Date.now(); - return this; -}; - -TxProposal.prototype.getSent = function() { - return this.sentTs; -} - -TxProposal.prototype.setCopayers = function(pubkeyToCopayerMap) { - var newCopayer = {}, - oldCopayers = {}, - newSignedBy = {}, - readOnlyPeers = {}, - isNew = 1; - - for (var k in this.signedBy) { - oldCopayers[k] = 1; - isNew = 0; - }; - - if (isNew == 0) { - if (!this.creator || !this.createdTs) - throw new Error('Existing TX has no creator'); - - if (!this.signedBy[this.creator]) - throw new Error('Existing TX is not signed by creator'); - - - if (Object.keys(this.signedBy).length === 0) - throw new Error('Existing TX has no signatures'); - } - - - var iSig = this.getSignersPubKeys(); - for (var i in iSig) { - var copayerId = pubkeyToCopayerMap[iSig[i]]; - - if (!copayerId) - throw new Error('Found unknown signature') - - if (oldCopayers[copayerId]) { - //Already have it. Do nothing - } else { - newCopayer[copayerId] = Date.now(); - delete oldCopayers[i]; - } - } - - if (Object.keys(newCopayer).length > 1) - throw new Error('New TX must have only 1 new signature'); - - // Handler creator / createdTs. - // from senderId, and must be signed by senderId * DISABLED* - // - if (isNew) { - this.creator = Object.keys(newCopayer)[0]; - this.seenBy[this.creator] = this.createdTs = Date.now(); - } - - //Ended. Update this - _.extend(this.signedBy, newCopayer); - - // signedBy has preference over rejectedBy - for (var i in this.signedBy) { - delete this.rejectedBy[i]; - } - - return Object.keys(newCopayer); -}; - -//This should be on bitcore / Transaction -TxProposal.prototype.countSignatures = function() { - var tx = this.builder.build(); - var ret = 0; - for (var i in tx.ins) { - ret += tx.countInputSignatures(i); - } - return ret; -}; - -module.exports = TxProposal; diff --git a/js/models/TxProposals.js b/js/models/TxProposals.js deleted file mode 100644 index 77dbcbccd..000000000 --- a/js/models/TxProposals.js +++ /dev/null @@ -1,156 +0,0 @@ -'use strict'; - -var _ = require('lodash'); -var preconditions = require('preconditions').singleton(); - -var bitcore = require('bitcore'); -var util = bitcore.util; -var Transaction = bitcore.Transaction; -var Script = bitcore.Script; -var Key = bitcore.Key; -var buffertools = bitcore.buffertools; - -var log = require('../util/log'); -var TxProposal = require('./TxProposal');; - -function TxProposals(opts) { - opts = opts || {}; - this.walletId = opts.walletId; - this.network = opts.networkName === 'livenet' ? - bitcore.networks.livenet : bitcore.networks.testnet; - this.txps = {}; -} - -// fromObj => from a trusted source -TxProposals.fromObj = function(o, forceOpts) { - var ret = new TxProposals({ - networkName: o.networkName, - walletId: o.walletId, - }); - - o.txps.forEach(function(o2) { - try { - var t = TxProposal.fromObj(o2, forceOpts); - } catch (e) { - log.info('Ignoring corrupted TxProposal:', o2, e); - } - if (t && t.builder) { - var id = t.getId(); - ret.txps[id] = t; - } - - }); - return ret; -}; - -TxProposals.prototype.length = function() { - return Object.keys(this.txps).length; -}; - - -TxProposals.prototype.getNtxidsSince = function(sinceTs) { - preconditions.checkArgument(sinceTs); - var ret = []; - - for (var ii in this.txps) { - var txp = this.txps[ii]; - if (txp.createdTs >= sinceTs) - ret.push(ii); - } - return ret; -}; - - - -TxProposals.prototype.getNtxids = function() { - return Object.keys(this.txps); -}; - -TxProposals.prototype.deleteOne = function(ntxid) { - preconditions.checkState(this.txps[ntxid], 'Unknown TXP: ' + ntxid); - delete this.txps[ntxid]; -}; - -TxProposals.prototype.deleteAll = function() { - this.txps = {}; -}; - -TxProposals.prototype.deletePending = function(maxRejectCount) { - for (var ntxid in this.txps) { - if (this.txps[ntxid].isPending(maxRejectCount)) - delete this.txps[ntxid]; - }; -}; - -TxProposals.prototype.toObj = function() { - var ret = []; - for (var id in this.txps) { - var t = this.txps[id]; - if (!t.sent) - ret.push(t.toObj()); - } - return { - txps: ret, - walletId: this.walletId, - networkName: this.network.name, - }; -}; - - - -// Add a LOCALLY CREATED (trusted) tx proposal -TxProposals.prototype.add = function(txp) { - var ntxid = txp.getId(); - this.txps[ntxid] = txp; - return ntxid; -}; - - -TxProposals.prototype.exist = function(ntxid) { - return this.txps[ntxid] ? true : false; -}; - - -TxProposals.prototype.get = function(ntxid) { - var ret = this.txps[ntxid]; - if (!ret) - throw new Error('Unknown TXP: ' + ntxid); - - return ret; -}; - -//returns the unspent txid-vout used in PENDING Txs -TxProposals.prototype.getUsedUnspent = function(maxRejectCount) { - var ret = {}; - var self = this; - - _.each(this.txps, function(txp) { - if (!txp.isPending(maxRejectCount)) - return - - _.each(txp.builder.getSelectedUnspent(), function(u) { - ret[u.txid + ',' + u.vout] = 1; - }); - }); - return ret; -}; - -/** - * purge - * - * @param deleteAll - * @return {undefined} - */ -TxProposals.prototype.purge = function(deleteAll, maxRejectCount) { - var m = _.size(this.txps); - - if (deleteAll) { - this.deleteAll(); - } else { - this.deletePending(maxRejectCount); - } - var n = _.size(this.txps); - return m - n; -}; - -module.exports = TxProposals; diff --git a/js/models/Wallet.js b/js/models/Wallet.js deleted file mode 100644 index acfb49b34..000000000 --- a/js/models/Wallet.js +++ /dev/null @@ -1,2846 +0,0 @@ -'use strict'; - -var _ = require('lodash'); -var preconditions = require('preconditions').singleton(); -var inherits = require('inherits'); -var events = require('events'); -var async = require('async'); - -var bitcore = require('bitcore'); -var BIP21 = bitcore.BIP21; -var bignum = bitcore.Bignum; -var coinUtil = bitcore.util; -var buffertools = bitcore.buffertools; -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 log = require('../util/log'); -var cryptoUtil = require('../util/crypto'); -var httpUtil = require('../util/HTTP'); -var HDParams = require('./HDParams'); -var PublicKeyRing = require('./PublicKeyRing'); -var TxProposal = require('./TxProposal'); -var TxProposals = require('./TxProposals'); -var PrivateKey = require('./PrivateKey'); -var Async = require('./Async'); -var Insight = module.exports.Insight = require('./Insight'); -var copayConfig = require('../../config'); - -var TX_MAX_INS = 70; - - -/** - * @desc - * Wallet manages a private key for Copay, network and blockchain information. - * - * @TODO: Split this leviathan. - * - * @param {Object} opts - * @param {Network} opts.network - used to send and retrieve messages from - * copayers - * @param {Blockchain} opts.blockchain - source of truth for what happens in - * the blockchain (utxos, balances) - * @param {number} opts.requiredCopayers - number of required copayers to - * release funds - * @param {number} opts.totalCopayers - number of copayers in the wallet - * @param {boolean} opts.spendUnconfirmed - whether it's safe to spend - * unconfirmed outputs or not - * @param {PublicKeyRing} opts.publicKeyRing - an instance of {@link PublicKeyRing} - * @param {TxProposals} opts.txProposals - an instance of {@link TxProposals} - * @param {PrivateKey} opts.privateKey - an instance of {@link PrivateKey} - * @param {string} opts.version - the version of copay where this wallet was - * created - * @TODO: figure out if reconnectDelay is set in milliseconds - * @param {number} opts.reconnectDelay - amount of seconds to wait before - * attempting to reconnect - * @constructor - */ -function Wallet(opts) { - var self = this; - preconditions.checkArgument(opts); - - opts.reconnectDelay = opts.reconnectDelay || 500; - - var networkName = Wallet.obtainNetworkName(opts); - preconditions.checkState((opts.network && opts.blockchain) || networkName); - - opts.network = opts.network || Wallet._newAsync(opts.networkOpts[networkName]); - opts.blockchain = opts.blockchain || Wallet._newInsight(opts.blockchainOpts[networkName]);; - this.httpUtil = opts.httpUtil || httpUtil; - - //required params - ['network', 'blockchain', - 'requiredCopayers', 'totalCopayers', 'spendUnconfirmed', - 'publicKeyRing', 'txProposals', 'privateKey', 'version', - 'reconnectDelay' - ].forEach(function(k) { - preconditions.checkArgument(!_.isUndefined(opts[k]), 'MISSOPT: missing required option for Wallet: ' + k); - self[k] = opts[k]; - }); - - - this.id = opts.id || Wallet.getRandomId(); - this.secretNumber = opts.secretNumber || Wallet.getRandomSecretNumber(); - // TODO - // this.lock = new WalletLock(this.storage, this.id, opts.lockTimeOutMin); - this.settings = opts.settings || copayConfig.wallet.settings; - this.name = opts.name; - - this.publicKeyRing.walletId = this.id; - this.txProposals.walletId = this.id; - this.registeredPeerIds = []; - this.addressBook = opts.addressBook || {}; - this.publicKey = this.privateKey.publicHex; - this.syncedTimestamp = opts.syncedTimestamp || 0; - this.lastMessageFrom = {}; - - - var networkName = Wallet.obtainNetworkName(opts); - - preconditions.checkArgument(this.network.setHexNonce, 'Incorrect network parameter'); - preconditions.checkArgument(this.blockchain.getTransaction, 'Incorrect blockchain parameter'); - - - this.network.maxPeers = this.totalCopayers; - this.network.secretNumber = this.secretNumber; - - //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); - this.network.setHexNonces(opts.networkNonces); - - this.cache = { - paymentRequests: {}, - }; -} - -inherits(Wallet, events.EventEmitter); - - -Wallet.TX_NEW = 'txNew'; -Wallet.TX_PURGED = 'txPurged'; -Wallet.TX_ALL_PURGED = 'txAllPurged'; -Wallet.TX_REJECTED = 'txRejected'; -Wallet.TX_BROADCASTED = 'txBroadcasted'; -Wallet.TX_PROPOSAL_SENT = 'txProposalSent'; -Wallet.TX_SIGNED = 'txSigned'; -Wallet.TX_SIGNED_AND_BROADCASTED = 'txSignedAndBroadcasted'; - -Wallet.prototype.emitAndKeepAlive = function(args) { - var args = Array.prototype.slice.call(arguments); - log.debug('Wallet:' + this.getName() + ' Emitting:', args); - this.keepAlive(); - this.emit.apply(this, arguments); -}; - -/** - * @desc Fixed & Forced TransactionBuilder options, for genererating transactions. - * - * @static - * @property lockTime null - * @property signhash SIGHASH - * @property fee null (automatic) - * @property feeSat null - */ -Wallet.builderOpts = { - lockTime: null, - signhash: bitcore.Transaction.SIGHASH_ALL, - fee: undefined, - feeSat: undefined, -}; - -/** - * @desc static list with persisted properties of a wallet. - * These are the properties that get stored/read from localstorage - */ -Wallet.PERSISTED_PROPERTIES = [ - 'opts', - 'settings', - 'publicKeyRing', - 'txProposals', - 'privateKey', - 'addressBook', - 'syncedTimestamp', - 'secretNumber', -]; - -/* For compressed keys, m*73 + n*34 <= 496 */ -Wallet.COPAYER_PAIR_LIMITS = { - 1: 1, - 2: 2, - 3: 3, - 4: 4, - 5: 4, - 6: 4, - 7: 3, - 8: 3, - 9: 2, - 10: 2, - 11: 1, - 12: 1, -}; - -Wallet.getStoragePrefix = function() { - return 'wallet::'; -}; - -Wallet.getStorageKey = function(str) { - return Wallet.getStoragePrefix() + str; -}; - -Wallet.prototype.getStorageKey = function() { - return Wallet.getStorageKey(this.getId()); -}; - -/* for stubbing */ -Wallet._newInsight = function(opts) { - return new Insight(opts); -}; - -/* for stubbing */ -Wallet._newAsync = function(opts) { - return new Async(opts); -}; - -/** - * @desc Retrieve a random id for the wallet - * @TODO: Discuss changing to a UUID - * @return {string} 8 bytes, hexa encoded - */ -Wallet.getRandomId = function() { - var r = bitcore.SecureRandom.getPseudoRandomBuffer(8).toString('hex'); - return r; -}; - -/** - * @desc Retrieve a random secret number to secure wallet secret - * @return {string} 5 bytes, hexa encoded - */ -Wallet.getRandomSecretNumber = function() { - var r = bitcore.SecureRandom.getPseudoRandomBuffer(5).toString('hex') - return r; -}; - -/** - * @desc - * Get the maximum allowed number of required copayers. - * This is a limit imposed by the maximum allowed size of the scriptSig. - * @param {number} totalCopayers - the total number of copayers - * @return {number} - */ -Wallet.getMaxRequiredCopayers = function(totalCopayers) { - return Wallet.COPAYER_PAIR_LIMITS[totalCopayers]; -}; - -/** - * @desc obtain network name from serialized wallet - * @param {Object} wallet object - * @return {string} network name - */ -Wallet.obtainNetworkName = function(obj) { - return obj.networkName || - (obj.opts ? obj.opts.networkName : null) || - (obj.publicKeyRing ? (obj.publicKeyRing.networkName || obj.publicKeyRing.network.name) : null) || - (obj.privateKey ? obj.privateKey.networkName : null); -}; - - - -/** - * @desc Set the copayer id for the owner of this wallet - * @param {string} pubkey - the pubkey to set to the {@link Wallet#seededCopayerId} property - */ -Wallet.prototype.seedCopayer = function(pubKey) { - this.seededCopayerId = pubKey; -}; - - -/** - * @desc Handles an 'indexes' message. - * - * Processes the data using {@link HDParams#fromList} and merges it with the - * {@link Wallet#publicKeyRing}. - * - * @param {Object} data - the data received, {@see HDParams#fromList} - */ -Wallet.prototype._doOnIndexes = function(indexes, fromTxProposal) { - preconditions.checkArgument(indexes); - var inIndexes = HDParams.fromList(indexes); - var hasChanged = this.publicKeyRing.mergeIndexes(inIndexes); - if (hasChanged) { - - // If the new indexes come from TX proposals (change address) - // that addresses should be new. No need to clean cache. - if (!fromTxProposal) - this.clearUnspentCache(); - - this.subscribeToAddresses(); - this.emitAndKeepAlive('newAddresses'); - } -}; - - -Wallet.prototype._onIndexes = function(senderId, data) { - return this._doOnIndexes(data.indexes); -}; - -/** - * @desc - * Changes wallet settings. The settings format is: - * - * var settings = { - * unitName: 'bits', - * unitToSatoshi: 100, - * alternativeName: 'US Dollar', - * alternativeIsoCode: 'USD', - * }; - */ -Wallet.prototype.changeSettings = function(settings) { - this.settings = settings; - this.emitAndKeepAlive('settingsUpdated'); -}; - -/** - * @desc Locks other sessions from connecting to the wallet - * @see Async#lockIncommingConnections - */ -Wallet.prototype._lockIncomming = function() { - this.network.lockIncommingConnections(this.publicKeyRing.getAllCopayerIds()); -}; - -/** - * @desc - * Handles a 'PUBLICKEYRING' message from senderId. - * - * data.publicKeyRing is expected to be processed correctly by - * {@link PublicKeyRing#fromObj}. - * - * After successful deserialization, {@link Wallet#publicKeyRing} is merged - * with the received data, a call to {@link Wallet#store} is performed if the - * internal state has changed. - * - * This locks new incoming connections in case the public key ring is completed - * - * @param {string} senderId - the sender id - * @param {Object} data - the data recived, {@see HDParams#fromList} - * @param {Object} data.publicKeyRing - data to be deserialized into a {@link PublicKeyRing} - * using {@link PublicKeyRing#fromObj} - * @emits connectionError - */ -Wallet.prototype._onPublicKeyRing = function(senderId, data) { - log.debug('Wallet:' + this.id + ' RECV PUBLICKEYRING:', data); - - var inPKR = PublicKeyRing.fromUntrustedObj(data.publicKeyRing); - var wasIncomplete = !this.publicKeyRing.isComplete(); - var hasChanged; - - try { - hasChanged = this.publicKeyRing.merge(inPKR, true); - } catch (e) { - log.warn('Wallet:' + this.id, e); - return; - } - if (hasChanged) { - - if (wasIncomplete) { - this.sendPublicKeyRing(); - } - if (this.publicKeyRing.isComplete()) { - this._lockIncomming(); - this.subscribeToAddresses(); - this.emitAndKeepAlive('ready'); - } else { - this.emitAndKeepAlive('publicKeyRingUpdated'); - } - } -}; - -/** - * @desc - * Retrieves a keymap from a transaction proposal set extracts a maps from - * public key to cosignerId for each signed input of the transaction proposal. - * - * @param {TxProposals} txp - the transaction proposals - * @return {Object} [pubkey] -> copayerId - */ -Wallet.prototype._getPubkeyToCopayerMap = function(txp) { - preconditions.checkArgument(txp); - var inSig0, keyMapAll = {}, - self = this; - - var signersPubKeys = txp.getSignersPubKeys(); - _.each(signersPubKeys, function(inputSignersPubKey, i) { - var keyMap = self.publicKeyRing.copayersForPubkeys(inputSignersPubKey, txp.inputChainPaths); - - if (_.size(keyMap) !== _.size(inputSignersPubKey)) - throw new Error('Signature does not match known copayers'); - - _.extend(keyMapAll, keyMap); - - // From here -> only to check that all inputs have the same sigs - var inSigArr = _.values(keyMap); - var inSig = JSON.stringify(inSigArr.sort()); - - if (!inSig0) { - inSig0 = inSig; - } else { - if (inSig !== inSig0) - throw new Error('found inputs with different signatures'); - } - }); - return keyMapAll; -}; - -/** - * @callback transactionCallback - * @param {error} error - * @param {number} transaction ID (if sent) - */ - -/** - * @desc - * Asynchronously check with the blockchain if a given transaction was sent. - * - * @param {string} ntxid - the transaction proposal - * @param {transactionCallback} cb - */ -Wallet.prototype._checkIfTxIsSent = function(ntxid, cb) { - var txp = this.txProposals.get(ntxid); - var tx = txp.builder.build(); - - //not used anymore - //var txHex = tx.serialize().toString('hex'); - - - //Use calcHash NOT getHash which could be cached. - var txid = bitcore.util.formatHashFull(tx.calcHash()); - - log.debug('Checking if transactions was broadcasted... ID:' + txid); - - this.blockchain.getTransaction(txid, function(err, tx) { - return cb(err, !err ? txid : null); - }); -}; - -/** - * - * @desc Set Incomming Transaction Proposal seen status - * and send `seen` messages to peers if aplicable. - * @param ntxid - */ -Wallet.prototype._setTxProposalSeen = function(txp) { - if (!txp.getSeen(this.getMyCopayerId())) { - txp.setSeen(this.getMyCopayerId()); - this.sendSeen(txp.getId()); - } -}; - - - -/** - * @desc updates Tx Proposal Sent status by checking the blockchain - * - * @param ntxid - * @param {transactionCallback} cb - */ -Wallet.prototype._updateTxProposalSent = function(txp, cb) { - var self = this; - this._checkIfTxIsSent(txp.getId(), function(err, txid) { - if (err) return cb(err); - - if (txid) { - txp.setSent(txid); - } - if (cb) - return cb(null, txid, txid ? Wallet.TX_BROADCASTED : null); - }); -}; - - -/** - * _processTxProposalPayPro - * - * @desc Process and incoming PayPro TX Proposal. Fetchs the payment request - * from the merchant. - * - * @param mergeInfo Proposals merge information, as returned by TxProposals.merge - * @return {fetchPaymentRequestCallback} - */ -Wallet.prototype._processTxProposalPayPro = function(txp, cb) { - var self = this; - - if (!txp.paymentProtocolURL) - return cb(); - - log.info('Received a Payment Protocol TX Proposal'); - self.fetchPaymentRequest({ - url: txp.paymentProtocolURL - }, function(err, merchantData) { - if (err) return cb(err); - - // This will verify current TXP data vs. merchantData (e.g., out addresses) - try { - txp.addMerchantData(merchantData); - } catch (e) { - log.error(e); - err = 'BADPAYPRO: ' + e.toString(); - } - return cb(err); - }); -}; - -/** - * @desc Process an NEW incoming transaction proposal. Runs safety and sanity checks on it. - * - * @param mergeInfo Proposals merge information, as returned by TxProposals.merge - * @return {errCallback} - */ -Wallet.prototype._processIncomingNewTxProposal = function(txp, cb) { - var self = this; - - var ntxid = txp.getId(); - self._processTxProposalPayPro(txp, function(err) { - if (err) return cb(err); - - // Disabled, not been shown on the UX now. - //self._setTxProposalSeen(txp); - - var tx = txp.builder.build(); - if (tx.isComplete() && !txp.getSent()) - self._updateTxProposalSent(txp); - return cb(); - }); -}; - - -/* only for stubbing */ -Wallet.prototype._txProposalFromUntrustedObj = function(data, opts) { - return TxProposal.fromUntrustedObj(data, opts); -}; - -/** - * @desc - * Handles a NEW 'TXPROPOSAL' network message - * - * @param {string} senderId - the id of the sender - * @param {Object} data - the data received - * @param {Object} data.txProposal - first parameter for {@link TxProposals#merge} - * @emits txProposalEvent - */ -Wallet.prototype._onTxProposal = function(senderId, data) { - preconditions.checkArgument(data.txProposal); - var self = this; - - if (data.indexes) { - this._doOnIndexes(data.indexes, true); - } - - try { - var incomingTx = self._txProposalFromUntrustedObj(data.txProposal, Wallet.builderOpts); - var incomingNtxid = incomingTx.getId(); - } catch (e) { - log.warn(e); - return; - } - - if (this.txProposals.exist(incomingNtxid)) { - log.warn('Ignoring existing tx Proposal:' + incomingNtxid); - return; - } - - self._processIncomingNewTxProposal(incomingTx, function(err) { - if (err) { - log.warn('Corrupt TX proposal received from:', senderId, err.toString()); - return; - } - - - var pubkeyToCopayerMap = self._getPubkeyToCopayerMap(incomingTx); - incomingTx.setCopayers(pubkeyToCopayerMap); - - self.txProposals.add(incomingTx); - self.emitAndKeepAlive('txProposalEvent', { - type: Wallet.TX_NEW, - cId: senderId, - }); - }); -}; - - -Wallet.prototype._onSignature = function(senderId, data) { - var self = this; - try { - var localTx = this.txProposals.get(data.ntxid); - } catch (e) { - log.info('Ignoring signature for unknown tx Proposal:' + data.ntxid); - return; - }; - localTx.addSignature(senderId, data.signatures); - self.emitAndKeepAlive('txProposalEvent', { - type: Wallet.TX_SIGNED, - cId: senderId, - }); - self.issueTxIfComplete(data.ntxid, function(err, txid) {}); -}; - -/** - * @desc - * Handle a REJECT message received - * - * @param {string} senderId - * @param {Object} data - * @param {string} data.ntxid - * @emits txProposalEvent - */ -Wallet.prototype._onReject = function(senderId, data) { - preconditions.checkState(data.ntxid); - log.debug('Wallet:' + this.id + ' RECV REJECT:', data); - - try { - var txp = this.txProposals.get(data.ntxid); - } catch (e) { - log.info(e); - }; - - if (txp) { - if (txp.signedBy[senderId]) - throw new Error('Received Reject for an already signed TX from:' + senderId); - - txp.setRejected(senderId); - this.emitAndKeepAlive('txProposalEvent', { - type: Wallet.TX_REJECTED, - cId: senderId, - txId: data.ntxid - }); - } -}; - -/** - * @desc - * Handle a SEEN message received - * - * @param {string} senderId - * @param {Object} data - * @param {string} data.ntxid - * @emits txProposalEvent - */ -Wallet.prototype._onSeen = function(senderId, data) { - preconditions.checkState(data.ntxid); - try { - var txp = this.txProposals.get(data.ntxid); - } catch (e) {}; - if (txp) { - txp.setSeen(senderId); - this.emitAndKeepAlive('txProposalEvent', { - type: Wallet.TX_SEEN, - cId: senderId, - txId: data.ntxid - }); - } -}; - -/** - * @desc - * Handle a ADDRESSBOOK message received - * - * @param {string} senderId - * @param {Object} data - * @param {Object} data.addressBook - * @emits addressBookUpdated - * @emits txProposalEvent - */ -Wallet.prototype._onAddressBook = function(senderId, data) { - if (!data.addressBook || !_.isObject(data.addressBook)) - return; - - var self = this, - hasChange; - _.each(data.addressBook, function(value, key) { - if (key && !self.addressBook[key] && _.isString(key) && Address.validate(key)) { - - self.addressBook[key] = _.pick(value, ['createdTs', 'label']); - - // Force author to senderId. - self.addressBook[key].copayerId = senderId; - - hasChange = true; - } - }); - - if (hasChange) { - this.emitAndKeepAlive('addressBookUpdated'); - } -}; - -Wallet.prototype.updateSyncedTimestamp = function(ts) { - preconditions.checkArgument(ts); - preconditions.checkArgument(_.isNumber(ts)); - preconditions.checkArgument(ts > 2999999999999, 'use microseconds'); - this.syncedTimestamp = ts; -}; - - -/** - * @desc Called when there are no messages in the server - * Triggers a call to {@link Wallet#sendWalletReady} - */ -Wallet.prototype._onNoMessages = function() { - if (this.isComplete()) { - log.debug('Wallet:' + this.getName() + ' No messages at the server. Requesting peer sync from: ' + (this.syncedTimestamp + 1)); - this.sendWalletReady(null, parseInt((this.syncedTimestamp + 1) / 1000000)); - } -}; - -/** - * @desc Demultiplexes a new message received through the wire - * - * @param {string} senderId - the sender id - * @param {Object} data - the received object - * @param {number} ts - the timestamp when this object was received - * - * @emits corrupt - */ -Wallet.prototype._onData = function(senderId, data, ts) { - preconditions.checkArgument(senderId); - preconditions.checkArgument(data); - preconditions.checkArgument(data.type); - preconditions.checkArgument(ts); - preconditions.checkArgument(_.isNumber(ts)); - - log.debug('Wallet:' + this.getName() + ' RECV:', data.type, data, senderId); - - this.updateSyncedTimestamp(ts); - - if (data.type !== 'walletId' && this.id !== data.walletId) { - log.debug('Wallet:' + this.id + ' Received corrupt message:', data) - this.emitAndKeepAlive('corrupt', senderId); - return; - } - - switch (data.type) { - // This handler is repeaded on WalletFactory (#join). TODO - case 'walletId': - this.sendWalletReady(senderId); - break; - case 'walletReady': - if (this.lastMessageFrom[senderId] !== 'walletReady') { - log.debug('Wallet:' + this.id + ' peer Sync received. since: ' + (data.sinceTs || 0)); - this.sendPublicKeyRing(senderId); - this.sendAddressBook(senderId); - this.sendAllTxProposals(senderId, data.sinceTs); // send old txps - } - break; - case 'publicKeyRing': - this._onPublicKeyRing(senderId, data); - break; - case 'reject': - this._onReject(senderId, data); - break; - case 'seen': - this._onSeen(senderId, data); - break; - case 'txProposal': - this._onTxProposal(senderId, data); - break; - case 'signature': - this._onSignature(senderId, data); - break; - case 'indexes': - this._onIndexes(senderId, data); - break; - case 'addressbook': - this._onAddressBook(senderId, data); - break; - // unused messages - case 'disconnect': - //case 'an other unused message': - break; - default: - throw new Error('unknown message type received: ' + data.type + ' from: ' + senderId) - } - this.lastMessageFrom[senderId] = data.type; - -}; - -/** - * @desc Handles a connect message - * @param {string} newCopayerId - the new copayer in the wallet - * @emits connect - */ -Wallet.prototype._onConnect = function(newCopayerId) { - if (newCopayerId) { - log.debug('Wallet:' + this.id + '#### Setting new COPAYER:', newCopayerId); - this.sendWalletId(newCopayerId); - } - - var peerID = this.network.peerFromCopayer(newCopayerId); - this.emitAndKeepAlive('connect', peerID); -}; - -/** - * @desc Returns the network name for this wallet ('testnet' or 'livenet') - * @return {string} - */ -Wallet.prototype.getNetworkName = function() { - return this.publicKeyRing.network.name; -}; - -/** - * @return {bool} - */ -Wallet.prototype.isTestnet = function() { - return this.publicKeyRing.network.name === 'testnet'; -}; - - - -/** - * @desc Serialize options into an object - * @return {Object} with keys id, spendUnconfirmed, - * requiredCopayers, totalCopayers, name, - * version - */ -Wallet.prototype._optsToObj = function() { - var obj = { - id: this.id, - spendUnconfirmed: this.spendUnconfirmed, - requiredCopayers: this.requiredCopayers, - totalCopayers: this.totalCopayers, - name: this.name, - version: this.version, - networkName: this.getNetworkName(), - }; - - return obj; -}; - -/** - * @desc Retrieve the copayerId pubkey for a given index - * @param {number=} index - the index of the copayer, ours by default - * @return {string} hex-encoded pubkey - */ -Wallet.prototype.getCopayerId = function(index) { - return this.publicKeyRing.getCopayerId(index || 0); -}; - -/** - * @desc Get my own pubkey - * @return {string} hex-encoded pubkey - */ -Wallet.prototype.getMyCopayerId = function() { - - if (!this._myId) - this._myId = this.getCopayerId(0); - - return this._myId; //copayer id is hex of a public key -}; - -/** - * @desc Retrieve my private key - * @return {string} hex-encoded private key - */ -Wallet.prototype.getMyCopayerIdPriv = function() { - return this.privateKey.getIdPriv(); //copayer idpriv is hex of a private key -}; - -/** - * @desc Get my own nickname - * @return {string} copayer nickname - */ -Wallet.prototype.getMyCopayerNickname = function() { - return this.publicKeyRing.nicknameForCopayer(this.getMyCopayerId()); -}; - -/** - * @desc Returns the secret value for other users to join this wallet - * @return {string} my own pubkey, base58 encoded - */ -Wallet.prototype.getSecretNumber = function() { - return this.secretNumber; -}; - -/** - * @desc Returns the secret number used to prevent MitM attacks from Insight - * @return {string} - */ -Wallet.prototype.getSecret = function() { - var buf = new Buffer( - this.getMyCopayerId() + - this.getSecretNumber() + - (this.getNetworkName() === 'livenet' ? '00' : '01'), - 'hex'); - var str = Base58Check.encode(buf); - return str; -}; - -/** - * @desc Returns an object with a pubKey value, an hex representation - * of a public key - * @param {string} secretB - the secret to be base58-decoded - * @return {Object} - */ -Wallet.decodeSecret = function(secretB) { - try { - var secret = Base58Check.decode(secretB); - var pubKeyBuf = secret.slice(0, 33); - var secretNumber = secret.slice(33, 38); - var networkName = secret.slice(38, 39).toString('hex') === '00' ? 'livenet' : 'testnet'; - return { - pubKey: pubKeyBuf.toString('hex'), - secretNumber: secretNumber.toString('hex'), - networkName: networkName, - } - } catch (e) { - log.debug(e.message); - return false; - } -}; - - -Wallet.prototype._setupBlockchainHandlers = function() { - - var self = this; - self.blockchain.removeAllListeners(); - self.subscribeToAddresses(); - - log.debug('Setting Blockchain listeners for', this.getName()); - self.blockchain.on('reconnect', function(attempts) { - log.debug('Wallet:' + self.id + 'blockchain reconnect event'); - self.clearUnspentCache(); - self.emitAndKeepAlive('insightReconnected'); - }); - - self.blockchain.on('disconnect', function() { - log.debug('Wallet:' + self.id + 'blockchain disconnect event'); - self.emitAndKeepAlive('insightError'); - }); - - self.blockchain.on('tx', function(tx) { - log.debug('Wallet:' + self.id + ' blockchain tx event'); - var addresses = self.getAddresses(); - // This should always be >=0 - if (_.indexOf(addresses, tx.address) >= 0) { - self.clearUnspentCache(); - self.emitAndKeepAlive('tx', tx.address, self.addressIsChange(tx.address)); - } - }); - - if (!self.spendUnconfirmed) { - - // TODO HERE should only clean utxos if there are some wallet - // transactions waiting for confirmation (ie confirmation < min confirmation) - self.clearUnspentCache(); - - self.blockchain.on('block', self.emitAndKeepAlive.bind(self, 'balanceUpdated')); - } -} - -Wallet.prototype._setupNetworkHandlers = function() { - var self = this; - - var net = this.network; - net.removeAllListeners(); - net.on('connect', self._onConnect.bind(self)); - net.on('data', self._onData.bind(self)); - net.on('no_messages', self._onNoMessages.bind(self)); - net.on('connect_error', function() { - self.emitAndKeepAlive('connectionError'); - }); -}; - -/** - * @desc Sets up the networking with other peers. - * - * @emits connect - * @emits data - * - * @emits ready - * - */ -Wallet.prototype.netStart = function() { - var self = this; - - if (self.netStarted) - return; - - - self._setupBlockchainHandlers(); - self.netStarted = true; - - if (!this.isShared()) { - self.emitAndKeepAlive('ready'); - return; - } - - self._setupNetworkHandlers(); - - var myId = self.getMyCopayerId(); - var myIdPriv = self.getMyCopayerIdPriv(); - var startOpts = { - copayerId: myId, - privkey: myIdPriv, - maxPeers: self.totalCopayers, - syncedTimestamp: this.syncedTimestamp || 0, - secretNumber: self.secretNumber, - }; - - var wasIncomplete; - if (this.publicKeyRing.isComplete()) { - this._lockIncomming(this.publicKeyRing.getAllCopayerIds()); - } else { - //Partially complete wallet. - if (this.publicKeyRing.getAllCopayerIds().length > 1) { - this.network.setCopayers(this.publicKeyRing.getAllCopayerIds()); - wasIncomplete = true; - } - } - - log.debug('Wallet:' + self.id + ' Starting network.'); - this.network.start(startOpts, function() { - //Partially complete wallet. - if (wasIncomplete) { - log.debug('Incomplete wallet opened:' + self.getName() + '. forced peer sync from 0'); - self.sendWalletReady(null, 0); - } - self.emitAndKeepAlive(self.isComplete() ? 'ready' : 'waitingCopayers'); - }); -}; - -/** - * @desc Retrieves the public keys of all the copayers in the ring - * @return {string[]} hex-encoded public keys of copayers - */ -Wallet.prototype.getRegisteredCopayerIds = function() { - var l = this.publicKeyRing.registeredCopayers(); - var copayers = []; - for (var i = 0; i < l; i++) { - var cid = this.getCopayerId(i); - copayers.push(cid); - } - return copayers; -}; - -/** - * @desc Retrieves the public keys of all the peers in the network - * @TODO: Isn't this deprecated? Now that we don't use peerjs - * - * @return {string[]} hex-encoded public keys of copayers - */ -Wallet.prototype.getRegisteredPeerIds = function() { - var l = this.publicKeyRing.registeredCopayers(); - if (this.registeredPeerIds.length !== l) { - this.registeredPeerIds = []; - var copayers = this.getRegisteredCopayerIds(); - for (var i = 0; i < l; i++) { - var cid = copayers[i]; - var pid = this.network.peerFromCopayer(cid); - this.registeredPeerIds.push({ - peerId: pid, - copayerId: cid, - nick: this.publicKeyRing.nicknameForCopayer(cid), - index: i, - }); - } - } - return this.registeredPeerIds; -}; - -/** - * @TODO: Review design of this call - * @desc Send a keepalive to this wallet's {@link WalletLock} instance. - * - * @emits locked - in case the wallet is opened in another instance - */ -Wallet.prototype.keepAlive = function() { - var self = this; - - - // this.lock.keepAlive(function(err) { - // if (err) { - // log.debug(err); - // self.emitAndKeepAlive('locked', null, 'Wallet appears to be openned on other browser instance. Closing this one.'); - // } - // }); -}; - - - -Wallet.prototype.getId = function() { - return this.id; -}; - -/** - * @desc Serialize the wallet into a plain object. - * @return {Object} - */ -Wallet.prototype.toObj = function() { - var optsObj = this._optsToObj(); - - var walletObj = { - opts: optsObj, - settings: this.settings, - networkNonce: this.network.getHexNonce(), //yours - networkNonces: this.network.getHexNonces(), //copayers - publicKeyRing: this.publicKeyRing.toObj(), - txProposals: this.txProposals.toObj(), - privateKey: this.privateKey ? this.privateKey.toObj() : undefined, - addressBook: this.addressBook, - syncedTimestamp: this.syncedTimestamp || 0, - secretNumber: this.secretNumber, - }; - - return walletObj; -}; - -/** - * @desc: returns the sizes, by component, of a wallet, in bytes. - * - * @return {object} sizes by component name and 'total' for the total wallet size. - */ -Wallet.prototype.sizes = function() { - var obj = this.toObj(); - var sizes = {}, - total = 0; - _.each(obj, function(val, key) { - var s = JSON.stringify(val).length; - sizes[key] = s; - total += s; - }); - sizes.total = total; - return sizes; -}; - -Wallet.fromUntrustedObj = function(obj, readOpts) { - obj = _.clone(obj); - var o = {}; - _.each(Wallet.PERSISTED_PROPERTIES, function(p) { - o[p] = obj[p]; - }); - - return Wallet.fromObj(o, readOpts); -}; - -/** - * @desc Retrieve the wallet state from a trusted object - * - * @param {Object} o - * @param {Object[]} o.addressBook - Stores known associations of bitcoin addresses to names - * @param {Object} o.privateKey - Private key to be deserialized by {@link PrivateKey#fromObj} - * @param {string} o.networkName - 'livenet' or 'testnet' - * @param {Object} o.publicKeyRing - PublicKeyRing to be deserialized by {@link PublicKeyRing#fromObj} - * @param {number} o.syncedTimestamp - ts of the last synced message with insifht (in microseconds, as insight returns ts) - * @param {Object} o.txProposals - TxProposals to be deserialized by {@link TxProposals#fromObj} - * @param {string} o.nickname - user's nickname - * - * @param readOpts.network - * @param readOpts.blockchain - * @param readOpts.{string[]} skipFields - parameters to ignore when importing - */ -Wallet.fromObj = function(o, readOpts) { - - preconditions.checkArgument(readOpts.networkOpts); - preconditions.checkArgument(readOpts.blockchainOpts); - - var networkOpts = readOpts.networkOpts; - var blockchainOpts = readOpts.blockchainOpts; - var skipFields = readOpts.skipFields || []; - - - if (skipFields) { - _.each(skipFields, function(k) { - if (o[k]) { - delete o[k]; - } else { - throw new Error('unknown field:' + k); - } - }); - } - - var networkName = Wallet.obtainNetworkName(o); - - - // TODO Why moving everything to opts. This needs refactoring. - // - // clone opts - - if (!o.opts) { - return null; - } - - var opts = JSON.parse(JSON.stringify(o.opts)); - opts.addressBook = o.addressBook; - opts.settings = o.settings; - - - if (o.privateKey) { - opts.privateKey = PrivateKey.fromObj(o.privateKey); - } else { - opts.privateKey = new PrivateKey({ - networkName: networkName - }); - } - - opts.secretNumber = o.secretNumber; - - if (o.publicKeyRing) { - opts.publicKeyRing = PublicKeyRing.fromObj(o.publicKeyRing); - } else { - opts.publicKeyRing = new PublicKeyRing({ - networkName: networkName, - requiredCopayers: opts.requiredCopayers, - totalCopayers: opts.totalCopayers, - }); - opts.publicKeyRing.addCopayer( - opts.privateKey.deriveBIP45Branch().extendedPublicKeyString(), - opts.nickname - ); - } - - if (o.txProposals) { - opts.txProposals = TxProposals.fromObj(o.txProposals, Wallet.builderOpts); - } else { - opts.txProposals = new TxProposals({ - networkName: networkName, - }); - } - - opts.syncedTimestamp = opts.publicKeyRing.isComplete() ? o.syncedTimestamp || 0 : 0; - opts.blockchainOpts = readOpts.blockchainOpts; - opts.networkOpts = readOpts.networkOpts; - - return new Wallet(opts); -}; - - -/** - * @desc sends a message to peers - * @param {string[]} recipients - the pubkey of the recipients of the message. Null for sending to all peers. - * @param {Object} obj - the data to be sent to them. - * @param {String} obj.type - Type of the message to be send - */ -Wallet.prototype._sendToPeers = function(recipients, obj) { - if (!this.isShared()) return; - log.info('Wallet:' + this.getName() + ' ### Sending ' + obj.type); - log.debug('Sending:', recipients, obj); - - this.network.send(recipients, obj); -}; - -/** - * @desc Send the set of TxProposals to peers - * @param {string[]} recipients - the pubkeys of the recipients - */ -Wallet.prototype.sendAllTxProposals = function(recipients, sinceTs) { - var ntxids = sinceTs ? this.txProposals.getNtxidsSince(sinceTs) : this.txProposals.getNtxids(); - var self = this; - _.each(ntxids, function(ntxid, key) { - self.sendTxProposal(ntxid, recipients); - }); -}; - -/** - * @desc Send a TxProposal identified by transaction id to a set of recipients - * @param {string} ntxid - the transaction proposal id - * @param {string[]} [recipients] - the pubkeys of the recipients - */ -Wallet.prototype.sendTxProposal = function(ntxid, recipients) { - preconditions.checkArgument(ntxid); - var indexes = HDParams.serialize(this.publicKeyRing.indexes); - this._sendToPeers(recipients, { - type: 'txProposal', - txProposal: this.txProposals.get(ntxid).toObjTrim(), - walletId: this.id, - indexes: indexes, - }); -}; - -/** - * @desc Notifies other peers that a transaction proposal was seen - * @param {string} ntxid - */ -Wallet.prototype.sendSeen = function(ntxid) { - preconditions.checkArgument(ntxid); - this._sendToPeers(null, { - type: 'seen', - ntxid: ntxid, - walletId: this.id, - }); -}; - -/** - * @desc Notifies other peers that a transaction proposal was rejected - * @param {string} ntxid - */ -Wallet.prototype.sendReject = function(ntxid) { - preconditions.checkArgument(ntxid); - - this._sendToPeers(null, { - type: 'reject', - ntxid: ntxid, - walletId: this.id, - }); -}; - - -/** - * @desc Send a signature for a TX Proposal - * @param {string} ntxid - */ -Wallet.prototype.sendSignature = function(ntxid) { - preconditions.checkArgument(ntxid); - - var txp = this.txProposals.get(ntxid); - var signatures = txp.getMySignatures(); - preconditions.checkState(signatures && signatures.length); - - this._sendToPeers(null, { - type: 'signature', - ntxid: ntxid, - signatures: signatures, - walletId: this.id, - }); -}; - - -/** - * @desc Notify other peers that a wallet has been backed up and it's ready to be used - * @param {string[]} [recipients] - the pubkeys of the recipients - */ -Wallet.prototype.sendWalletReady = function(recipients, sinceTs) { - this._sendToPeers(recipients, { - type: 'walletReady', - walletId: this.id, - sinceTs: sinceTs - }); -}; - -/** - * @desc Notify other peers of the walletId - * @TODO: Why is this needed? Can't everybody just calculate the walletId? - * @param {string[]} [recipients] - the pubkeys of the recipients - */ -Wallet.prototype.sendWalletId = function(recipients) { - this._sendToPeers(recipients, { - type: 'walletId', - walletId: this.id, - opts: this._optsToObj(), - networkName: this.getNetworkName(), - }); -}; - -/** - * @desc Send the current PublicKeyRing to other recipients - * @param {string[]} [recipients] - the pubkeys of the recipients - */ -Wallet.prototype.sendPublicKeyRing = function(recipients) { - var publicKeyRingObj = this.publicKeyRing.toTrimmedObj(); - - this._sendToPeers(recipients, { - type: 'publicKeyRing', - publicKeyRing: publicKeyRingObj, - walletId: this.id, - }); -}; - -/** - * @desc Send the current indexes of our public key ring to other peers - * @param {string[]} recipients - the pubkeys of the recipients - */ -Wallet.prototype.sendIndexes = function(recipients) { - var indexes = HDParams.serialize(this.publicKeyRing.indexes); - this._sendToPeers(recipients, { - type: 'indexes', - indexes: indexes, - walletId: this.id, - }); -}; - -/** - * sendAddressBook - * @desc Send our addressBook to other recipients - * - * @param {string[]} recipients - the pubkeys of the recipients - * @param onlyKey - * @return {undefined} - */ -Wallet.prototype.sendAddressBook = function(recipients, onlyKey) { - var toSend = [], - myId = this.getMyCopayerId(); - - if (onlyKey && this.addressBook[onlyKey]) { - toSend = {}; - toSend[onlyKey] = this.addressBook[onlyKey]; - } else { - toSend = _.filter(this.addressBook, function(entry) { - return entry.copayerId === myId; - }); - } - if (_.isEmpty(toSend)) return; - - this._sendToPeers(recipients, { - type: 'addressbook', - addressBook: toSend, - walletId: this.id, - }); -}; - -/** - * @desc Retrieve this wallet's name - * @return {string} - */ -Wallet.prototype.getName = function() { - return this.name || this.id; -}; - -/** - * @desc Generate a new address - * @param {boolean} isChange - whether to generate a change address or a receive address - * @return {string[]} a list of all the addresses generated so far for the wallet - */ -Wallet.prototype._doGenerateAddress = function(isChange) { - var addr = this.publicKeyRing.generateAddress(isChange, this.publicKey); - this.subscribeToAddresses(); - this.emitAndKeepAlive('newAddresses'); - return addr; -}; - -/** - * @desc Generate a new address - * @param {boolean} isChange - whether to generate a change address or a receive address - * @return {string[]} a list of all the addresses generated so far for the wallet - */ -Wallet.prototype.generateAddress = function(isChange) { - var addr = this._doGenerateAddress(isChange); - this.sendIndexes(); - return addr; -}; - - -/** - * @desc get list of actions (see {@link getPendingTxProposals}) - */ -Wallet.prototype._getActionList = function(txp) { - preconditions.checkArgument(txp); - - var self = this; - var peers = []; - - _.each(self.getRegisteredCopayerIds(), function(copayerId) { - var actions = { - rejected: txp.rejectedBy[copayerId], - sign: txp.signedBy[copayerId], - seen: txp.seenBy[copayerId], - create: (txp.creator === copayerId) ? txp.createdTs : null, - }; - peers.push({ - cId: copayerId, - actions: actions, - }); - }); - return peers; -}; - -/** - * @desc Retrieve Pendings Transaction proposals (see {@link TxProposals}) - * @return {Object[]} each object returned represents a transaction proposal - */ -Wallet.prototype.getPendingTxProposalsCount = function() { - var self = this; - var txps = this.txProposals.txps; - var maxRejectCount = this.maxRejectCount(); - var myId = this.getMyCopayerId(); - var pending = 0, - pendingForUs = 0; - - _.each(txps, function(inTxp, ntxid) { - if (!inTxp.isPending(maxRejectCount)) - return; - // TODO: are the uxtos availables? - // - pending++; - - if (!inTxp.signedBy[myId] && !inTxp.rejectedBy[myId]) - pendingForUs++ - }); - - - return { - pending: pending, - pendingForUs: pendingForUs, - }; -}; - - -/** - * @desc Retrieve Pendings Transaction proposals (see {@link TxProposals}) - * @return {Object[]} each object returned represents a transaction proposal - */ -Wallet.prototype.getPendingTxProposals = function() { - var self = this; - var ret = []; - var txps = this.txProposals.txps; - var maxRejectCount = this.maxRejectCount(); - var satToUnit = 1 / this.settings.unitToSatoshi; - - _.each(txps, function(inTxp, ntxid) { - if (!inTxp.isPending(maxRejectCount) || (inTxp.sentTs && inTxp.isFullySigned())) - return; - - var txp = _.clone(inTxp); - txp.ntxid = ntxid; - - var addresses = {}; - var outs = JSON.parse(txp.builder.vanilla.outs); - outs.forEach(function(o) { - if (!addresses[o.address]) addresses[o.address] = 0; - addresses[o.address] += (o.amountSatStr || Math.round(o.amount * bitcore.util.COIN)); - }); - txp.outs = []; - _.each(addresses, function(value, address) { - txp.outs.push({ - address: address, - value: value * satToUnit - }); - }); - // extra fields - txp.fee = txp.builder.feeSat * satToUnit; - txp.missingSignatures = txp.builder.build().countInputMissingSignatures(0); - txp.actionList = self._getActionList(txp); - ret.push(txp); - }); - - return ret; -}; - -/** - * @desc Removes old transactions - * @param {boolean} deleteAll - if true, remove all the transactions - * @return {number} the number of deleted proposals - */ -Wallet.prototype.purgeTxProposals = function(deleteAll) { - var deleted = this.txProposals.purge(deleteAll, this.maxRejectCount()); - if (deleted) { - this.emitAndKeepAlive('txProposalEvent', { - type: deleteAll ? Wallet.TX_PURGED : Wallet.TX_ALL_PURGED, - cId: this.getMyCopayerId(), - }); - } - return deleted; -}; - -/** - * @desc Reject a proposal - * @param {string} ntxid the id of the transaction proposal to reject - * @emits txProposalsEvent - */ -Wallet.prototype.reject = function(ntxid, cb) { - var txp = this.txProposals.get(ntxid); - txp.setRejected(this.getMyCopayerId()); - this.sendReject(ntxid); - - this.emitAndKeepAlive('txProposalEvent', { - type: Wallet.TX_REJECTED, - cId: this.getMyCopayerId(), - }); - - // TODO this callback should be triggered by sendRejected, which is trully async - if (cb) - cb(null, Wallet.TX_REJECTED); -}; - -/** - * @callback signCallback - * @param {Error} error if any - * @param {number} Transaction ID or Transaction Proposal ID - * @param {status} Wallet.TX_* Status - */ - - -/** - * @desc Signs a transaction proposal - * @param {string} ntxid the id of the transaction proposal to sign - * @throws {Error} Could not sign proposal - * @throws {Error} Bad payment request - * @return {boolean} true if signing actually incremented the number of signatures - */ -Wallet.prototype.sign = function(ntxid) { - preconditions.checkState(!_.isUndefined(this.getMyCopayerId())); - - var txp = this.txProposals.get(ntxid); - var keys = this.privateKey.getForPaths(txp.inputChainPaths); - var signaturesAdded = txp.sign(keys, this.getMyCopayerId()); - if (!signaturesAdded) - return false; - - return true; -}; - -Wallet.prototype.issueTxIfComplete = function(ntxid, cb) { - var txp = this.txProposals.get(ntxid); - var tx = txp.builder.build(); - if (tx.isComplete()) { - this.issueTx(ntxid, cb); - } else { - return cb(); - } -}; - - -/** - * - * @desc signs and send or broadcast a transaction. - * In m-n wallets, - * if m==1 it will broadcast it to the Bitcoin Network - * if n>1 it will send the proposal to the peers - * - * @param ntxid Transaction Proposal Id - * @param {signCallback} cb - * @throws {Error} Could not sign proposal - * @emits txProposalEvent - */ -Wallet.prototype.signAndSend = function(ntxid, cb) { - var self = this; - - if (this.sign(ntxid)) { - - this.sendSignature(ntxid); - this.issueTxIfComplete(ntxid, function(err, txid, status) { - - // We did not broadcast the TX, only signed it. - if (!txid) { - self.emitAndKeepAlive('txProposalEvent', { - type: Wallet.TX_SIGNED, - cId: self.getMyCopayerId(), - }); - } - - return cb(err, txid, status ? status : Wallet.TX_SIGNED); - }); - } else { - return cb(new Error('Could not sign the proposal')); - } -}; - - -/** - * @desc Broadcast a tx proposal. In case of failure, check if the resulting - * transactions is already on the blockchain. - * - * @param ntxid - * @param cb - * @return {undefined} - */ -Wallet.prototype.broadcastToBitcoinNetwork = function(ntxid, cb) { - var self = this; - var txp = this.txProposals.get(ntxid); - - var tx = txp.builder.build(); - preconditions.checkState(tx.isComplete(), 'tx is not complete'); - - var txHex = tx.serialize().toString('hex'); - - log.info('Wallet:' + this.id + ' Broadcasting Transaction ntxid:' + ntxid); - log.debug('\tRaw transaction: ', txHex); - - this.blockchain.broadcast(txHex, function(err, txid) { - if (err || !txid) { - log.debug('Wallet:' + self.getName() + ' Send failed:' + err); - - self._checkIfTxIsSent(ntxid, function(err, txid) { - self.emitAndKeepAlive('balanceUpdated'); - return cb(err, txid); - }); - } else { - log.info('Wallet:' + self.getName() + ' broadcasted a TX! TXID:', txid); - return cb(null, txid); - } - }); -}; - -/** - * @desc Broadcasts a transaction to the blockchain, updates tx transactions - * sent status. If the tx proposal is a payment protocol request,it will also - * send the payment message to the server,and process the response. - * - * @param {string} ntxid - the transaction proposal id - * @param {string} txid - the transaction id on the blockchain - * @param {signCallback} cb - */ -Wallet.prototype.issueTx = function(ntxid, cb) { - var self = this; - - self.broadcastToBitcoinNetwork(ntxid, function(err, txid) { - if (err) return cb(err); - preconditions.checkState(txid); - - var txp = self.txProposals.get(ntxid); - txp.setSent(txid); - self.emitAndKeepAlive('txProposalEvent', { - type: Wallet.TX_BROADCASTED, - cId: self.getMyCopayerId(), - }); - - // PAYPRO: Payment message is optional, only if payment_url is set - // This is async. and will notify and update txp async. - if (txp.merchant && txp.merchant.pr.pd.payment_url) { - var data = self.createPayProPayment(txp); - self.sendPayProPayment(txp, data, function(err, data) { - if (err) return cb(err); - self.onPayProPaymentAck(ntxid, data); - }); - } - return cb(null, txid, Wallet.TX_BROADCASTED); - }); -}; - -/** - * @callback {fetchPaymentRequestCallback} - * @param {string=} err - an error, if any - * @param {Object} merchantData - object representing the payment request. Add described on BIP70 merchant_data - */ - -/** - * @desc Creates a Payment Protocol transaction - * @param {Object|string} options - if it's a string, parse it as the url - * @param {string} options.url the url for the transaction - * @return {fetchPaymentRequestCallback} cb - */ -Wallet.prototype.fetchPaymentRequest = function(options, cb) { - preconditions.checkArgument(_.isObject(options)); - preconditions.checkArgument(options.url); - preconditions.checkArgument(options.url.indexOf('http') == 0, 'Bad PayPro URL given:' + options.url); - var self = this; - - if (self.cache.paymentRequests[options.url]) - return cb(null, self.cache.paymentRequests[options.url]); - - this.httpUtil.request({ - method: 'GET', - url: options.url, - headers: { - 'Accept': PayPro.PAYMENT_REQUEST_CONTENT_TYPE - }, - responseType: 'arraybuffer' - }) - .success(function(rawData) { - log.info('PayPro Request done successfully. Parsing response') - - var merchantData, err; - try { - merchantData = self.parsePaymentRequest(options, rawData); - } catch (e) { - err = e - }; - - log.debug('PayPro request data', merchantData); - - self.cache.paymentRequests[options.url] = merchantData; - return cb(err, merchantData); - }) - .error(function(data, status) { - log.debug('Server did not return PaymentRequest.\nXHR status: ' + status); - return cb(new Error('Status: ' + status)); - }); -}; - - -/** - * _addOutputsToMerchantData - * - * @desc parses merchant_data internal output representation and stores - * the result in merchant_data.outs = [{address: xx, amountSatStr: xx}], - * to be compatible with TransactionBuilder. - *` - * @param merchantData BIP70 merchant_data (from the payment request) - * @throws {Error} PayPro: Unsupported inputs - * @return {undefined} - */ -Wallet.prototype._addOutputsToMerchantData = function(merchantData) { - - var total = bignum(0); - var outs = {}; - - _.each(merchantData.pr.pd.outputs, 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; - 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); - - var a = addr[0].toString(); - outs[a] = bignum.fromBuffer(v, { - endian: 'big', - size: 1 - }).add(outs[a] || bignum(0)); - - total = total.add(bignum.fromBuffer(v, { - endian: 'big', - size: 1 - })); - }); - - // for now we only support PayPro with 1 output. - if (_.size(outs) !== 1) - throw new Error('PayPro: Unsupported outputs'); - - var out = _.pairs(outs)[0]; - - merchantData.outs = [{ - address: out[0], - amountSatStr: out[1].toString(10), - }]; - merchantData.total = total.toString(10); - - // If user is granted the privilege of choosing - // their own amount, add it to the tx. - if (merchantData.total == "0" && options.amount) { - merchant.outs[0].amountSatStr = merchantData.total = options.amount; - } -}; - -/** - * @desc Analyzes a payment request and generate merchantData - * @param {Object} options - * @param {string} options.url url where the pay request was acquired - * @param {string} options.amount Only used if pay requesst allow user to set the amount - * @param {PayProRequest} rawData - */ -Wallet.prototype.parsePaymentRequest = function(options, rawData) { - var self = this; - - var data = PayPro.PaymentRequest.decode(rawData); - var paypro = new PayPro(); - var pr = paypro.makePaymentRequest(data); - var ver = pr.get('payment_details_version'); - var pki_type = pr.get('pki_type'); - var pki_data = pr.get('pki_data'); - var details = pr.get('serialized_payment_details'); - var sig = pr.get('signature'); - - var certs = PayPro.X509Certificates.decode(pki_data); - certs = certs.certificate; - - // Verify Signature - var trust = pr.verify(true); - - if (!trust.verified) { - throw 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 total = bignum('0', 10).toString(10); - var merchantData = { - pr: { - payment_details_version: ver, - pki_type: pki_type, - pki_data: certs, - pd: { - network: network, - outputs: outputs.map(function(output) { - return { - amount: output.get('amount'), - 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') - } - }; - }), - time: time, - expires: expires, - memo: memo || 'This server would like some BTC from you.', - payment_url: payment_url, - merchant_data: merchant_data ? merchant_data.toString('hex') : null - }, - signature: sig.toString('hex'), - ca: trust.caName, - untrusted: !trust.caTrusted, - selfSigned: trust.selfSigned - }, - expires: expires, - request_url: options.url, - domain: /^(?:https?)?:\/\/([^\/:]+).*$/.exec(options.url)[1], - total: total, - expirationDate: expires ? new Date(expires * 1000) : null, - }; - - this._addOutputsToMerchantData(merchantData, options.amount); - - return merchantData; -}; - -/** - * _getPayProRefundOutputs - * Create refund outputs for a PayPro Payment Message - * Uses current transaction's change address. - * - * @param txp - * @return {undefined} - */ -Wallet.prototype._getPayProRefundOutputs = function(txp) { - var pkr = this.publicKeyRing; - var amount = +txp.merchant.total.toString(10); - - var output = new PayPro.Output(); - var opts = JSON.parse(txp.builder.vanilla.opts); - if (!opts.remainderOut) { - log.warn('no remainder set. Not setting refund in PayPro'); - return; - } - var addrStr = opts.remainderOut.address; - var addr = new bitcore.Address(addrStr); - var script = bitcore.Script.createP2SH(addr.payload()).getBuffer(); - log.debug('PayPro refund address set to:' + addrStr); - - output.set('script', script); - output.set('amount', amount); - return [output]; -}; - - -/** - * - * @desc Creates a Payment Protocol Payment message for the given TX Proposal - * @param txp Transaction Proposal - * @param txHex - * @return {undefined} - */ -Wallet.prototype.createPayProPayment = function(txp) { - - var tx = txp.builder.build(); - var txBuf = tx.serialize(); - - - // We send this to the serve after receiving a PaymentRequest - var pay = new PayPro(); - pay = pay.makePayment(); - - var merchant_data = txp.merchant.pr.pd.merchant_data; - if (merchant_data) { - merchant_data = new Buffer(merchant_data, 'hex'); - pay.set('merchant_data', merchant_data); - } - pay.set('transactions', [txBuf]); - - var refund_outputs = this._getPayProRefundOutputs(txp); - if (refund_outputs) - pay.set('refund_to', refund_outputs); - - // Unused for now - // options.memo = ''; - // pay.set('memo', options.memo); - - pay = pay.serialize(); - var buf = new ArrayBuffer(pay.length); - var view = new Uint8Array(buf); - for (var i = 0; i < pay.length; i++) { - view[i] = pay[i]; - } - - return view; -}; - - -/** - * onPayProPaymentAck - * - * @desc parse and process a Payment Protocol Payment Ack. Updates - * given TX Proposal with merchant's memo and send it to copayers - * - * @param ntxid ID of the Transaction Proposal - * @param rawData of the Payment Ack - * @emits paymentACK - (merchants's memo) - */ -Wallet.prototype.onPayProPaymentAck = function(ntxid, rawData) { - var data = PayPro.PaymentACK.decode(rawData); - var paypro = new PayPro(); - var ack = paypro.makePaymentACK(data); - var memo = ack.get('memo'); - log.debug('Payment Acknowledged!: %s', memo); - - var txp = this.txProposals.get(ntxid); - txp.paymentAckMemo = memo; - this.sendTxProposal(ntxid); - this.emitAndKeepAlive('paymentACK', memo); -}; - - -/** - * @desc Send a payment transaction to a merchant, complying with BIP70 - * on Acknoledge, updates the TX Proposal with server's memo and send it - * to peers - * - * @param {string} ntxid - the transaction proposal ID for with the - * - */ -Wallet.prototype.sendPayProPayment = function(txp, data, cb) { - var self = this; - - log.debug('Sending Payment Message to merchant server'); - var postInfo = { - method: 'POST', - url: txp.merchant.pr.pd.payment_url, - headers: { - // BIP-71 - 'Accept': PayPro.PAYMENT_ACK_CONTENT_TYPE, - 'Content-Type': PayPro.PAYMENT_CONTENT_TYPE - // XHR does not allow these: - // 'Content-Length': (pay.byteLength || pay.length) + '', - // 'Content-Transfer-Encoding': 'binary' - }, - // Technically how this should be done via XHR (used to - // be the ArrayBuffer, now you send the View instead). - data: data, - responseType: 'arraybuffer' - }; - - this.httpUtil.request(postInfo) - .success(function(rawData) { - return cb(null, rawData); - }) - .error(function(data, status) { - log.error('Sending payment notification: XHR status: ' + status); - return cb(new Error(status)); - }); -}; - -/** - * @desc Mark that a user has seen a given TxProposal - * @return {boolean} true if the internal state has changed - */ -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; -}; - -/** - * @desc Alias for {@link PublicKeyRing#getAddresses} - * @return {Buffer[]} - */ -Wallet.prototype.getAddresses = function() { - return this.publicKeyRing.getAddresses(); -}; - - -/** - * @desc gets the list of addresses, ordered for the caller: - * 1) himselfs first - * 2) receive address first - * 3) last created first - */ -Wallet.prototype.getAddressesOrdered = function() { - return this.publicKeyRing.getAddressesOrdered(this.publicKey); -}; - -/** - * @desc Alias for {@link PublicKeyRing#getAddresses} - * @return {Buffer[]} - */ -Wallet.prototype.getReceiveAddresses = function() { - return this.publicKeyRing.getReceiveAddresses(); -}; - - -Wallet.prototype.subscribeToAddresses = function() { - if (!this.publicKeyRing.isComplete()) return; - - var addresses = this.getAddresses(); - this.blockchain.subscribe(addresses); - log.debug('Wallet:' + this.getName() + ' Subscribed to:' + addresses.length + ' addresses'); -}; - -/** - * @desc Returns true if a given address was generated by deriving our master public key - * @return {boolean} - */ -Wallet.prototype.addressIsOwn = function(addrStr) { - return this.publicKeyRing.addressIsOwn(addrStr); -}; - - -/** - * @desc Returns true if a given address is a change address (remainder) - * @param addrStr - * @return {boolean} - */ -Wallet.prototype.addressIsChange = function(addrStr) { - return this.publicKeyRing.addressIsChange(addrStr); -}; - - - -/** - * Estimate a tx fee in satoshis given its input count - * (only used when spending all wallet funds) - */ -Wallet.estimatedFee = function(unspentCount) { - preconditions.checkArgument(_.isNumber(unspentCount)); - var estimatedSizeKb = Math.ceil((500 + unspentCount * 250) / 1024); - return parseInt(estimatedSizeKb * bitcore.TransactionBuilder.FEE_PER_1000B_SAT); -}; - - -/** - * @callback {getBalanceCallback} - * @param {string=} err - an error, if any - * @param {number} balance - total number of satoshis for all addresses - * @param {Object} balanceByAddr - maps string addresses to satoshis - * @param {number} safeBalance - total number of satoshis in UTXOs that are not part of any TxProposal - * @param {number} safeUnspentCount - total number of safe unspent Outputs that make this balance. - */ - -/** - * computeBalance - * - * @param safeUnspent - * @param unspent - * @param {getBalanceCallback} cb - */ -Wallet.prototype.computeBalance = function(safeUnspent, unspent, cb) { - var balance = 0; - var safeBalance = 0; - var balanceByAddr = {}; - var COIN = coinUtil.COIN; - - for (var i = 0; i < unspent.length; i++) { - var u = unspent[i]; - var amt = u.amount * COIN; - balance += amt; - balanceByAddr[u.address] = (balanceByAddr[u.address] || 0) + amt; - } - - // we multiply and divide by BIT to avoid rounding errors when adding - for (var a in balanceByAddr) { - balanceByAddr[a] = parseInt(balanceByAddr[a].toFixed(0), 10); - } - - balance = parseInt(balance.toFixed(0), 10); - - var safeUnspentCount = safeUnspent.length; - - for (var i = 0; i < safeUnspentCount; i++) { - var u = safeUnspent[i]; - var amt = u.amount * COIN; - safeBalance += amt; - } - - safeBalance = parseInt(safeBalance.toFixed(0), 10); - return cb(null, balance, balanceByAddr, safeBalance, safeUnspentCount); -}; - -/** - * @desc Returns the balances for all addresses in Satoshis - * @param {getBalanceCallback} cb - */ -Wallet.prototype.getBalance = function(cb) { - var self = this; - - this.getUnspent(function(err, safeUnspent, unspent) { - if (err) { - return cb(err); - } - self.computeBalance(safeUnspent, unspent, cb); - }); -}; - - -// See -// https://github.com/bitpay/copay/issues/1056 -// -// maxRejectCount should equal requiredCopayers -// strictly. -/** - * @desc Get the number of copayers that need to reject a transaction so it can't be signed - * @return {number} - */ -Wallet.prototype.maxRejectCount = function() { - return this.totalCopayers - this.requiredCopayers; -}; - - -Wallet.prototype.clearUnspentCache = function() { - log.debug('Cleaning unspent cache'); - this.cache.unspent = null; -}; - -/** - * @callback getUnspentCallback - * @desc Get a list of unspent transaction outputs - * @param {string} error - * @param {Object[]} safeUnspendList - * @param {Object[]} unspentList - * @param {getUnspentCallback} cb - */ - -Wallet.prototype.getUnspent = function(cb) { - var self = this; - - - if (self.cache.unspent != null) { - log.debug('Wallet ' + this.getName() + ': Get unspent cache hit'); - return self.computeUnspent(self.cache.unspent, cb); - } - - var addresses = this.getAddresses(); - log.debug('Wallet ' + this.getName() + ': Getting unspents from ' + addresses.length + ' addresses'); - this.blockchain.getUnspent(addresses, function(err, unspentList) { - if (err) - return cb(err); - - self.cache.unspent = unspentList; - return self.computeUnspent(self.cache.unspent, cb); - }); -}; - -/** - * - * @callback getUnspentCallback - * @param {string} error - * @param {Object[]} safeUnspendList - * @param {Object[]} unspentList - */ -/** - * computeUnspent - * - * @param unspentList List of unprocessed utxos. - * @param {getUnspentCallback} cb - */ -Wallet.prototype.computeUnspent = function(unspentList, cb) { - var self = this; - - var safeUnspendList = []; - var uu = this.txProposals.getUsedUnspent(this.maxRejectCount()); - - _.each(unspentList, function(u) { - var name = u.txid + ',' + u.vout; - if (!uu[name] && (self.spendUnconfirmed || u.confirmations >= 1)) - safeUnspendList.push(u); - }); - - return cb(null, safeUnspendList, unspentList); -}; - -/** - * spend - * - * @desc Spends coins from the wallet - * Create a Transaction Proposal and send it - * to copayers (broadcast it in a 1-x wallet) - * @param {object} opts - * @param {string} opts.toAddress address to send coins - * @param {number} opts.amountSat amount in satoshis - * @param {string} opts.comment optional transaction proposal private comment (for copayers) - * @param {string} opts.url optional (payment protocol URL). If this is given, toAddress will be ignored, and amount could be ignored or not, depending on the payment protocol request. - * @param {signCallback} cb - */ -Wallet.prototype.spend = function(opts, cb) { - preconditions.checkArgument(_.isObject(opts)); - log.debug('create Options', opts); - - var self = this; - var toAddress = opts.toAddress; - var amountSat = opts.amountSat; - var comment = opts.comment; - var merchantData = opts.merchantData; - - - // PayPro? With given merchant data - if (opts.merchantData && !opts.toAddress) { - if (!merchantData.outs[0].address) - return cb(new Error('BADPAYPRO')); - - opts.toAddress = merchantData.outs[0].address; - opts.amountSat = parseInt(merchantData.outs[0].amountSatStr); - return self.spend(opts, cb); - } - - // PayPro? Fetch payment data and recurse - var url = opts.url; - if (url && !opts.merchantData) { - return self.fetchPaymentRequest({ - url: url, - memo: comment, - amount: amountSat, - }, function(err, merchantData) { - if (err) return cb(err); - opts.merchantData = merchantData; - return self.spend(opts, cb); - }); - } - - preconditions.checkArgument(amountSat, 'no amount'); - preconditions.checkArgument(toAddress, 'no address'); - - this.getUnspent(function(err, safeUnspent) { - if (err) { - log.info(err); - return cb(new Error('Spend: Could not get list of UTXOs')); - } - - var ntxid, txp; - try { - txp = self._createTxProposal(toAddress, - amountSat, comment, safeUnspent, opts.builderOpts); - - if (opts.merchantData) { - txp.addMerchantData(opts.merchantData); - } - } catch (e) { - log.warn(e); - return cb(e); - } - - var ntxid = self.txProposals.add(txp); - if (!ntxid) { - return cb(new Error('Error creating the transaction')); - } - - log.debug('TXP Added: ', ntxid); - // Needs only one signature? Broadcast it! - if (!self.requiresMultipleSignatures()) - return self.issueTx(ntxid, cb); - - self.sendTxProposal(ntxid); - self.emitAndKeepAlive('txProposalEvent', { - type: Wallet.TX_PROPOSAL_SENT, - cId: self.getMyCopayerId(), - }); - return cb(null, ntxid, Wallet.TX_PROPOSAL_SENT); - }); -}; - -/** - * _getAddress - * Returns an Address object from an address string or a BIP21 URL.* - * @param address - * @return { bitcore.Address } - */ - -Wallet._getAddress = function(address) { - if (/ ^ bitcoin: /g.test(address)) { - return new BIP21(address).address; - } - return new Address(address); -}; - - -Wallet.prototype._getBuilder = function(opts) { - opts = opts || {}; - - if (!opts.remainderOut) { - opts.remainderOut = { - address: this._doGenerateAddress(true).toString() - }; - } - if (_.isUndefined(opts.spendUnconfirmed)) { - opts.spendUnconfirmed = this.spendUnconfirmed; - } - - for (var k in Wallet.builderOpts) { - opts[k] = Wallet.builderOpts[k]; - } - - return new Builder(opts); -}; - - -/* - * _createTxProposal - * Creates a transaction proposal and run many sanity checks - * - * @param toAddress - * @param amountSat - * @param comment (optional) - * @param utxos - * @param builderOpts bitcore.TransactionBuilder options(like spendUnconfirmed) - * @return {TxProposal} The newly created transaction proposal.* - * Throws errors on unexpected inputs. - */ - -Wallet.prototype._createTxProposal = function(toAddress, amountSat, comment, utxos, builderOpts) { - preconditions.checkArgument(toAddress); - preconditions.checkArgument(amountSat); - preconditions.checkArgument(_.isArray(utxos)); - preconditions.checkArgument(!comment || comment.length <= 100, 'Comment too long'); - - var pkr = this.publicKeyRing; - var priv = this.privateKey; - var addr = Wallet._getAddress(toAddress); - - preconditions.checkState(addr && addr.data && addr.isValid(), 'Bad address:' + addr.toString()); - - preconditions.checkArgument(addr.network().name === this.getNetworkName(), 'networkname mismatch'); - preconditions.checkState(pkr.isComplete(), 'pubkey ring incomplete'); - preconditions.checkState(priv, 'no private key'); - - var b = this._getBuilder(builderOpts); - - b.setUnspent(utxos) - .setOutputs([{ - address: addr.data, - amountSatStr: amountSat, - }]); - - var selectedUtxos = b.getSelectedUnspent(); - - if (selectedUtxos.length > TX_MAX_INS) - throw new Error('BIG: Resulting TX is too big:' + selectedUtxos.length + - ' inputs. Aborting'); - - - var inputChainPaths = selectedUtxos.map(function(utxo) { - return pkr.pathForAddress(utxo.address); - }); - b.setHashToScriptMap(pkr.getRedeemScriptMap(inputChainPaths)); - - - var tx = b.build(); - var myId = this.getMyCopayerId(); - var keys = priv.getForPaths(inputChainPaths); - return new TxProposal({ - inputChainPaths: inputChainPaths, - comment: comment, - builder: b, - creator: myId, - signWith: keys, - }); - - return txp; -}; - - -/** - * @desc Updates all the indexes for the current publicKeyRing. This scans - * the blockchain looking for transactions on derived addresses. - * - * @param {Function} callback - called when all indexes have been updated. Receives an error, if any, as first argument - */ -Wallet.prototype.updateIndexes = function(callback) { - var self = this; - if (!self.isComplete()) - return callback(); - log.debug('Wallet:' + this.id + ' Updating indexes...'); - var tasks = this.publicKeyRing.indexes.map(function(index) { - return function(callback) { - self.updateIndex(index, callback); - }; - }); - - async.parallel(tasks, function(err) { - if (err) callback(err); - log.debug('Wallet:' + self.id + ' Indexes updated'); - self.clearUnspentCache(); - self.subscribeToAddresses(); - self.emitAndKeepAlive('newAddresses'); - callback(); - }); -}; - -/** - * @desc Updates the lastly used index - * @param {Object} index - an index, as used by {@link PublicKeyRing} - * @param {Function} callback - called with no arguments when done updating - */ -Wallet.prototype.updateIndex = function(index, callback) { - var self = this; - var SCANN_WINDOW = 20; - self.indexDiscovery(index.changeIndex, true, index.copayerIndex, SCANN_WINDOW, function(err, changeIndex) { - if (err) return callback(err); - if (changeIndex != -1) - index.changeIndex = changeIndex + 1; - - self.indexDiscovery(index.receiveIndex, false, index.copayerIndex, SCANN_WINDOW, function(err, receiveIndex) { - if (err) return callback(err); - if (receiveIndex != -1) - index.receiveIndex = receiveIndex + 1; - callback(); - }); - }); -}; - -/** - * @desc Derive addresses using the given parameters - * - * @param {number} index - the index to start with - * @param {number} amount - number of addresses to derive - * @param {boolean} isChange - derive change addresses or receive addresses - * @param {number} copayerIndex - the index of the copayer for whom to derive addresses - * @return {string[]} the result of calling {@link PublicKeyRing#getAddress} - */ -Wallet.prototype.deriveAddresses = function(index, amount, isChange, copayerIndex) { - preconditions.checkArgument(amount); - preconditions.shouldBeDefined(copayerIndex); - - var ret = new Array(amount); - for (var i = 0; i < amount; i++) { - // TODO - ret[i] = this.publicKeyRing._getAddress(index + i, isChange, copayerIndex).toString(); - } - return ret; -}; - -/** - * @callback {indexDiscoveryCallback} - * @param {?} err - * @param {number} lastActivityIndex - */ -/** - * @desc Scans the block chain for the last index with activity for a copayer - * - * This function scans the publicKeyRing branch starting at index @start and reports the index with last activity, - * using a scan window of @gap. The argument @change defines the branch to scan: internal or external. - * Returns -1 if no activity is found in range. - * @param {number} start - the number for which to start scanning - * @param {boolean} change - whether to search for in the change branch or the receive branch - * @param {number} copayerIndex - the index of the copayer - * @param {number} gap - the maximum number of addresses to scan after the last active address - * @param {indexDiscoveryCallback} cb - callback - * @return {number} -1 if there's no activity in the range provided - */ -Wallet.prototype.indexDiscovery = function(start, change, copayerIndex, gap, cb) { - preconditions.shouldBeDefined(copayerIndex); - preconditions.checkArgument(gap); - var scanIndex = start; - var lastActive = -1; - var hasActivity = false; - - var self = this; - async.doWhilst( - function _do(next) { - // Optimize window to minimize the derivations. - var scanWindow = (lastActive == -1) ? gap : gap - (scanIndex - lastActive) + 1; - var addresses = self.deriveAddresses(scanIndex, scanWindow, change, copayerIndex); - self.blockchain.getActivity(addresses, function(err, actives) { - if (err) throw err; - - // Check for new activities in the newlly scanned addresses - var recentActive = actives.reduce(function(r, e, i) { - return e ? scanIndex + i : r; - }, lastActive); - hasActivity = lastActive != recentActive; - lastActive = recentActive; - scanIndex += scanWindow; - next(); - }); - }, - function _while() { - return hasActivity; - }, - function _finally(err) { - if (err) return cb(err); - cb(null, lastActive); - } - ); -}; - -/** - * @desc Closes the wallet and disconnects all services - */ -Wallet.prototype.close = function(cb) { - log.debug('## CLOSING Wallet: ' + this.id); - this.network.removeAllListeners(); - this.network.cleanUp(); - this.blockchain.removeAllListeners(); - this.blockchain.destroy(); - - // TODO - // this.lock.release(function() { - if (cb) return cb(); - // }); -}; - -/** - * @desc Returns the name of the network ('livenet' or 'testnet') - * @return {string} - */ -Wallet.prototype.getNetwork = function() { - return this.network; -}; - -/** - * @desc Throws an error if an address already exists in the address book - * @private - */ -Wallet.prototype._checkAddressBook = function(key) { - if (this.addressBook[key] && this.addressBook[key].copayerId != -1) { - throw new Error('This address already exists in your Address Book'); - } -}; - -/** - * @desc Add an entry to the address book - * - * @param {string} key - the address to be added - * @param {string} label - a name for the address - */ -Wallet.prototype.setAddressBook = function(key, label) { - this._checkAddressBook(key); - var copayerId = this.getMyCopayerId(); - var ts = Date.now(); - var newEntry = { - hidden: false, - createdTs: ts, - copayerId: copayerId, - label: label, - }; - this.addressBook[key] = newEntry; - this.sendAddressBook(null, key); - this.emitAndKeepAlive('addressBookUpdated'); -}; - -/** - * @desc Hides or unhides an address book entry - * @param {string} key - the address in the addressbook - */ -Wallet.prototype.toggleAddressBookEntry = function(key) { - if (!key) throw new Error('Key is required'); - this.addressBook[key].hidden = !this.addressBook[key].hidden; - this.emitAndKeepAlive('addressBookUpdated', true); -}; - -/** - * @desc Returns true if there are more than one cosigners - * @return {boolean} - */ -Wallet.prototype.isShared = function() { - return this.totalCopayers > 1; -}; - -/** - * @desc Returns true if more than one signature is required - * @return {boolean} - */ -Wallet.prototype.requiresMultipleSignatures = function() { - return this.requiredCopayers > 1; -}; - -/** - * @desc Returns true if the keyring is complete - * @return {boolean} - */ -Wallet.prototype.isComplete = function() { - return this.publicKeyRing.isComplete(); -}; - - -/** - * @desc Sets the version of this wallet object - * - * @param {string} version - the new version for the wallet - */ -Wallet.prototype.setVersion = function(version) { - this.version = version; - if (this.opts) { - this.opts.version = version; - } -}; - -/** - * @desc Return a list of past transactions - * - * @param {number} opts.currentPage - the desired page in the dataset - * @param {number} opts.itemsPerPage - number of items per page - * @return {Object} the list of transactions - */ -Wallet.prototype.getTransactionHistory = function(opts, cb) { - var self = this; - - if (_.isFunction(opts)) { - cb = opts; - opts = {}; - } - opts = opts || {}; - - var addresses = self.getAddresses(); - var proposals = self.txProposals.txps; - var satToUnit = 1 / self.settings.unitToSatoshi; - - var indexedProposals = _.indexBy(proposals, 'sentTxid'); - - - function extractInsOuts(tx) { - // Inputs - var inputs = _.map(tx.vin, function(item) { - return { - type: 'in', - address: item.addr, - isMine: self.addressIsOwn(item.addr), - isChange: self.addressIsChange(item.addr), - amountSat: item.valueSat, - } - }); - var outputs = _.map(tx.vout, function(item) { - var itemAddr; - // If classic multisig, ignore - if (item.scriptPubKey && item.scriptPubKey.addresses.length == 1) { - itemAddr = item.scriptPubKey.addresses[0]; - } - - return { - type: 'out', - address: itemAddr, - isMine: self.addressIsOwn(itemAddr), - isChange: self.addressIsChange(itemAddr), - label: self.addressBook[itemAddr] ? self.addressBook[itemAddr].label : undefined, - amountSat: parseInt((item.value * bitcore.util.COIN).toFixed(0)), - } - }); - - return inputs.concat(outputs); - }; - - function sum(items, filter) { - return _.reduce(_.where(items, filter), - function(memo, item) { - return memo + item.amountSat; - }, 0); - }; - - function decorateTx(tx) { - var items = extractInsOuts(tx); - - var amountIn = sum(items, { - type: 'in', - isMine: true - }); - - var amountOut = sum(items, { - type: 'out', - isMine: true, - isChange: false, - }); - - var amountOutChange = sum(items, { - type: 'out', - isMine: true, - isChange: true, - }); - - var fees = parseInt((tx.fees * bitcore.util.COIN).toFixed(0)); - var amount; - - - if (amountIn == (amountOut + amountOutChange + (amountIn > 0 ? fees : 0))) { - tx.action = 'moved'; - amount = amountOut; - } else { - amount = amountIn - amountOut - amountOutChange - (amountIn > 0 ? fees : 0); - tx.action = amount > 0 ? 'sent' : 'received'; - } - - if (tx.action == 'sent' || tx.action == 'moved') { - var firstOut = _.findWhere(items, { - type: 'out' - }); - if (firstOut) { - tx.labelTo = firstOut.label; - tx.addressTo = firstOut.address; - } - }; - - tx.amountSat = Math.abs(amount); - tx.amount = tx.amountSat * satToUnit; - tx.minedTs = !_.isNaN(tx.time) ? tx.time * 1000 : undefined; - - var proposal = indexedProposals[tx.txid]; - if (proposal) { - tx.comment = proposal.comment; - tx.sentTs = proposal.sentTs; - tx.merchant = proposal.merchant; - tx.peerActions = proposal.peerActions; - tx.paymentAckMemo = proposal.paymentAckMemo; - tx.actionList = self._getActionList(proposal); - } - }; - - function paginate(res, currentPage, itemsPerPage) { - if (!res) { - res = { - totalItems: 0, - items: [], - }; - }; - - var r = { - itemsPerPage: itemsPerPage || res.totalItems, - currentPage: currentPage || 1, - nbItems: res.totalItems, - items: res.items, - }; - r.nbPages = r.itemsPerPage != 0 ? Math.ceil(r.nbItems / r.itemsPerPage) : 1; - return r; - }; - - if (addresses.length > 0) { - var from = (opts.currentPage - 1) * opts.itemsPerPage; - var to = opts.currentPage * opts.itemsPerPage; - if (!_.isNumber(from) || _.isNaN(from)) from = 0; - if (!_.isNumber(to) || _.isNaN(to)) to = null; - - self.blockchain.getTransactions(addresses, from, to, function(err, res) { - if (err) return cb(err); - - _.each(res.items, function(tx) { - if (tx) { - decorateTx(tx); - } - }); - - return cb(null, paginate(res, opts.currentPage, opts.itemsPerPage)); - }); - } else { - return cb(null, paginate(null, opts.currentPage, opts.itemsPerPage)); - } -}; - -Wallet.prototype.exportEncrypted = function(password, opts) { - opts = opts || {}; - var crypto = opts.cryptoUtil || cryptoUtil; - return crypto.encrypt(password, this.toObj()); -}; - -module.exports = Wallet; diff --git a/js/models/WalletLock.js b/js/models/WalletLock.js deleted file mode 100644 index 77514d7e0..000000000 --- a/js/models/WalletLock.js +++ /dev/null @@ -1,116 +0,0 @@ -'use strict'; - -var preconditions = require('preconditions').singleton(); - -function WalletLock(storage, walletId, timeoutMin) { - preconditions.checkArgument(storage); - preconditions.checkArgument(walletId); - - this.storage = storage; - this.timeoutMin = timeoutMin || 5; - this.key = WalletLock._keyFor(walletId); -} - -WalletLock.prototype.getSessionId = function(cb) { - preconditions.checkArgument(cb); - var self = this; - - self.sessionStorage.getItem('sessionId', function(sessionId) { - if (sessionId) - return cb(sessionId); - - sessionId = bitcore.SecureRandom.getRandomBuffer(8).toString('hex'); - self.sessionStorage.setItem('sessionId', sessionId, function() { - return cb(sessionId); - }); - }); -}; - - -WalletLock.prototype.init = function(cb) { - preconditions.checkArgument(cb); - var self = this; - - self.storage.getSessionId(function(sid) { - preconditions.checkState(sid); - - self.sessionId = sid; - cb(); - }); -}; - -WalletLock._keyFor = function(walletId) { - return 'lock' + '::' + walletId; -}; - -WalletLock.prototype._isLockedByOther = function(cb) { - var self = this; - - this.storage.getGlobal(this.key, function(json) { - var wl = json ? JSON.parse(json) : null; - if (!wl || !wl.expireTs) - return cb(false); - - var expiredSince = Date.now() - wl.expireTs; - if (expiredSince >= 0) - return cb(false); - - var isMyself = wl.sessionId === self.sessionId; - - if (isMyself) - return cb(false); - - // Seconds remainding - return cb(parseInt(-expiredSince / 1000)); - }); -}; - - -WalletLock.prototype._setLock = function(cb) { - preconditions.checkArgument(cb); - preconditions.checkState(this.sessionId); - var self = this; - - this.storage.setGlobal(this.key, { - sessionId: this.sessionId, - expireTs: Date.now() + this.timeoutMin * 60 * 1000, - }, function() { - - cb(null); - }); -}; - - -WalletLock.prototype._doKeepAlive = function(cb) { - preconditions.checkArgument(cb); - preconditions.checkState(this.sessionId); - - var self = this; - - this._isLockedByOther(function(t) { - if (t) - return cb(new Error('LOCKED: Wallet is locked for ' + t + ' srcs')); - - self._setLock(cb); - }); -}; - - - -WalletLock.prototype.keepAlive = function(cb) { - var self = this; - - if (!self.sessionId) { - return self.init(self._doKeepAlive.bind(self, cb)); - }; - - return this._doKeepAlive(cb); -}; - - -WalletLock.prototype.release = function(cb) { - this.storage.removeGlobal(this.key, cb); -}; - - -module.exports = WalletLock; diff --git a/js/plugins/EncryptedInsightStorage.js b/js/plugins/EncryptedInsightStorage.js deleted file mode 100644 index ce377796c..000000000 --- a/js/plugins/EncryptedInsightStorage.js +++ /dev/null @@ -1,59 +0,0 @@ -var cryptoUtil = require('../util/crypto'); -var InsightStorage = require('./InsightStorage'); -var inherits = require('inherits'); -var log = require('../util/log'); -var SEPARATOR = '%^#@'; - -function EncryptedInsightStorage(config) { - InsightStorage.apply(this, [config]); -} -inherits(EncryptedInsightStorage, InsightStorage); - -EncryptedInsightStorage.prototype._brokenDecrypt = function(body) { - var key = cryptoUtil.kdf(this.password + this.email, 'mjuBtGybi/4=', 100); - log.debug('Trying legacy decrypt') - var decryptedJson = cryptoUtil.decrypt(key, body); - return decryptedJson; -}; - -EncryptedInsightStorage.prototype.resendVerificationEmail = function(callback) { - InsightStorage.prototype.resendVerificationEmail.apply(this, [callback]); -}; - -EncryptedInsightStorage.prototype.getItem = function(name, callback) { - var self = this; - InsightStorage.prototype.getItem.apply(this, [name, - function(err, body, headers) { - if (err) { - return callback(err); - } - var decryptedJson = cryptoUtil.decrypt(self.email + SEPARATOR + self.password, body); - - if (!decryptedJson) { - log.debug('Could not decrypt value using current decryption schema'); - decryptedJson = self._brokenDecrypt(body); - } - - if (!decryptedJson) { - log.debug('Could not decrypt value.'); - return callback('PNOTFOUND'); - } - return callback(null, decryptedJson, headers); - } - ]); -}; - -EncryptedInsightStorage.prototype.setItem = function(name, value, callback) { - var record = cryptoUtil.encrypt(this.email + SEPARATOR + this.password, value); - InsightStorage.prototype.setItem.apply(this, [name, record, callback]); -}; - -EncryptedInsightStorage.prototype.removeItem = function(name, callback) { - InsightStorage.prototype.removeItem.apply(this, [name, callback]); -}; - -EncryptedInsightStorage.prototype.clear = function(callback) { - InsightStorage.prototype.clear.apply(this, [callback]); -}; - -module.exports = EncryptedInsightStorage; diff --git a/js/plugins/EncryptedLocalStorage.js b/js/plugins/EncryptedLocalStorage.js deleted file mode 100644 index c14cd4008..000000000 --- a/js/plugins/EncryptedLocalStorage.js +++ /dev/null @@ -1,77 +0,0 @@ -var cryptoUtil = require('../util/crypto'); -var log = require('../util/log'); -var LocalStorage = require('./LocalStorage'); -var inherits = require('inherits'); -var preconditions = require('preconditions').singleton(); - -var SEPARATOR = '@#$'; - -function EncryptedLocalStorage(config) { - LocalStorage.apply(this, [config]); -} -inherits(EncryptedLocalStorage, LocalStorage); - - -EncryptedLocalStorage.prototype._brokenDecrypt = function(body) { - var key = cryptoUtil.kdf(this.password + this.email, 'mjuBtGybi/4=', 100); - log.debug('Trying legacy decrypt') - var decryptedJson = cryptoUtil.decrypt(key, body); - return decryptedJson; -}; - - - -EncryptedLocalStorage.prototype._brokenDecryptUndef = function(body) { - var badkey = undefined + SEPARATOR + undefined; - return cryptoUtil.decrypt(badkey, body); -}; - - - - -EncryptedLocalStorage.prototype.getItem = function(name, callback) { - var self = this; - preconditions.checkState(self.email); - - LocalStorage.prototype.getItem.apply(this, [name, - function(err, body) { - - - var decryptedJson = cryptoUtil.decrypt(self.email + SEPARATOR + self.password, body); - if (!decryptedJson) { - log.debug('Could not decrypt value using current decryption schema'); - decryptedJson = self._brokenDecrypt(body); - } - - if (!decryptedJson) { - decryptedJson = self._brokenDecryptUndef(body); - } - - if (!decryptedJson) { - log.debug('Could not decrypt value.'); - return callback('PNOTFOUND'); - } - - return callback(null, decryptedJson); - } - ]); -}; - -EncryptedLocalStorage.prototype.setItem = function(name, value, callback) { - if (!_.isString(value)) { - value = JSON.stringify(value); - } - var record = cryptoUtil.encrypt(this.email + SEPARATOR + this.password, value); - LocalStorage.prototype.setItem.apply(this, [name, record, callback]); -}; - -EncryptedLocalStorage.prototype.removeItem = function(name, callback) { - LocalStorage.prototype.removeItem.apply(this, [name, callback]); -}; - -EncryptedLocalStorage.prototype.clear = function(callback) { - LocalStorage.prototype.clear.apply(this, [callback]); -}; - - -module.exports = EncryptedLocalStorage; diff --git a/js/plugins/GoogleDrive.js b/js/plugins/GoogleDrive.js deleted file mode 100644 index 08634a279..000000000 --- a/js/plugins/GoogleDrive.js +++ /dev/null @@ -1,301 +0,0 @@ -'use strict'; - -var preconditions = require('preconditions').singleton(); -var loaded = 0; -var SCOPES = 'https://www.googleapis.com/auth/drive'; -var log = require('../util/log'); - -function GoogleDrive(config) { - preconditions.checkArgument(config && config.clientId, 'No clientId at GoogleDrive config'); - - this.clientId = config.clientId; - this.home = config.home || 'copay'; - this.idCache = {}; - - this.type = 'DB'; - - this.scripts = [{ - then: this.initLoaded.bind(this), - src: 'https://apis.google.com/js/client.js?onload=InitGoogleDrive' - }]; - - this.isReady = false; - this.useImmediate = true; - this.ts = 100; -}; - -window.InitGoogleDrive = function() { - log.debug('googleDrive loadeded'); //TODO - loaded = 1; -}; - -GoogleDrive.prototype.init = function() {}; - -/** - * Called when the client library is loaded to start the auth flow. - */ -GoogleDrive.prototype.initLoaded = function() { - if (!loaded) { - window.setTimeout(this.initLoaded.bind(this), 500); - } else { - window.setTimeout(this.checkAuth.bind(this), 1); - } -} - -/** - * Check if the current user has authorized the application. - */ -GoogleDrive.prototype.checkAuth = function() { - - log.debug('Google Drive: Checking Auth'); - gapi.auth.authorize({ - 'client_id': this.clientId, - 'scope': SCOPES, - 'immediate': this.useImmediate, - }, - this.handleAuthResult.bind(this)); -}; - -GoogleDrive.prototype.setCredentils = function(email, password, opts, callback) { -}; - -/** - * Called when authorization server replies. - */ -GoogleDrive.prototype.handleAuthResult = function(authResult) { - var self = this; - log.debug('Google Drive: authResult', authResult); //TODO - - if (authResult.error) { - if (authResult.error) { - self.useImmediate = false; - return this.checkAuth(); - }; - throw new Error(authResult.error); - } - - gapi.client.load('drive', 'v2', function() { - self.isReady = true; - }); -} - -GoogleDrive.prototype.checkReady = function() { - if (!this.isReady) - throw new Error('goggle drive is not ready!'); -}; - -GoogleDrive.prototype._httpGet = function(theUrl) { - var accessToken = gapi.auth.getToken().access_token; - var xmlHttp = null; - - xmlHttp = new XMLHttpRequest(); - xmlHttp.open("GET", theUrl, false); - xmlHttp.setRequestHeader('Authorization', 'Bearer ' + accessToken); - xmlHttp.send(null); - return xmlHttp.responseText; -} - -GoogleDrive.prototype.createItem = function(name, value, callback) { - this.getItem(name, function(err, retrieved) { - if (err || !retrieved) { - return this.setItem(name, value, callback); - } else { - return callback('EEXISTS'); - } - }); -}; - -GoogleDrive.prototype.getItem = function(k, cb) { - //console.log('[googleDrive.js.95:getItem:]', k); //TODO - var self = this; - - self.checkReady(); - self._idForName(k, function(kId) { - // console.log('[googleDrive.js.89:kId:]', kId); //TODO - if (!kId) - return cb(null); - - - var args = { - 'path': '/drive/v2/files/' + kId, - 'method': 'GET', - }; - // console.log('[googleDrive.js.95:args:]', args); //TODO - - var request = gapi.client.request(args); - request.execute(function(res) { - // console.log('[googleDrive.js.175:res:]', res); //TODO - if (!res || !res.downloadUrl) - return cb(null); - - return cb(self._httpGet(res.downloadUrl)); - }); - - }); -}; - -GoogleDrive.prototype.setItem = function(k, v, cb) { - // console.log('[googleDrive.js.111:setItem:]', k, v); //TODO - var self = this; - - self.checkReady(); - self._idForName(this.home, function(parentId) { - preconditions.checkState(parentId); - // console.log('[googleDrive.js.118:parentId:]', parentId); //TODO - self._idForName(k, function(kId) { - - // console.log('[googleDrive.js.105]', parentId, kId); //TODO - - - var boundary = '-------314159265358979323846'; - var delimiter = "\r\n--" + boundary + "\r\n"; - var close_delim = "\r\n--" + boundary + "--"; - - var metadata = { - 'title': k, - 'mimeType': 'application/octet-stream', - 'parents': [{ - 'id': parentId - }], - }; - - var base64Data = btoa(v); - var multipartRequestBody = - delimiter + - 'Content-Type: application/json\r\n\r\n' + - JSON.stringify(metadata) + - delimiter + - 'Content-Type: application/octet-stream \r\n' + - 'Content-Transfer-Encoding: base64\r\n' + - '\r\n' + - base64Data + - close_delim; - - var args = { - 'path': '/upload/drive/v2/files' + (kId ? '/' + kId : ''), - 'method': kId ? 'PUT' : 'POST', - 'params': { - 'uploadType': 'multipart', - }, - 'headers': { - 'Content-Type': 'multipart/mixed; boundary="' + boundary + '"' - }, - 'body': multipartRequestBody - } - // console.log('[googleDrive.js.148:args:]', args); //TODO - - var request = gapi.client.request(args); - request.execute(function(ret) { - return cb(ret.kind === 'drive#file' ? null : new Error('error saving file on drive')); - }); - }); - }); -}; - -GoogleDrive.prototype.removeItem = function(k, cb) { - var self = this; - - self.checkReady(); - self._idForName(this.home, function(parentId) { - preconditions.checkState(parentId); - self._idForName(k, function(kId) { - - var args = { - 'path': '/drive/v2/files/' + kId, - 'method': 'DELETE', - }; - var request = gapi.client.request(args); - request.execute(function() { - if (cb) - cb(); - }); - }); - }); -}; - -GoogleDrive.prototype.clear = function() { - this.checkReady(); - throw new Error('clear not implemented'); -}; - - -GoogleDrive.prototype._mkdir = function(cb) { - preconditions.checkArgument(cb); - var self = this; - - log.debug('Creating drive folder ' + this.home); - - var request = gapi.client.request({ - 'path': '/drive/v2/files', - 'method': 'POST', - 'body': JSON.stringify({ - 'title': this.home, - 'mimeType': "application/vnd.google-apps.folder", - }), - }); - request.execute(function() { - self._idForName(self.home, cb); - }); -}; - - -GoogleDrive.prototype._idForName = function(name, cb) { - // console.log('[googleDrive.js.199:_idForName:]', name); //TODO - preconditions.checkArgument(name); - preconditions.checkArgument(cb); - var self = this; - - if (!self.isReady) { - log.debug('Waiting for Google Drive'); - self.ts = self.ts * 1.5; - return setTimeout(self._idForName.bind(self, name, cb), self.ts); - } - - if (self.idCache[name]) { - // console.log('[googleDrive.js.212:] FROM CACHE', name, self.idCache[name]); //TODO - return cb(self.idCache[name]); - } - - log.debug('GoogleDrive Querying for: ', name); //TODO - var args; - - var idParent = name == this.home ? 'root' : self.idCache[this.home]; - - if (!idParent) { - return self._mkdir(function() { - self._idForName(name, cb); - }); - } - // console.log('[googleDrive.js.177:idParent:]', idParent); //TODO - preconditions.checkState(idParent); - - args = { - 'path': '/drive/v2/files', - 'method': 'GET', - 'params': { - 'q': "title='" + name + "' and trashed = false and '" + idParent + "' in parents", - } - }; - - var request = gapi.client.request(args); - request.execute(function(res) { - var i = res.items && res.items[0] ? res.items[0].id : false; - if (i) - self.idCache[name] = i; - // console.log('[googleDrive.js.238] CACHING ' + name + ':' + i); //TODO - return cb(self.idCache[name]); - }); -}; - -GoogleDrive.prototype._checkHomeDir = function(cb) { - var self = this; - - this._idForName(this.home, function(homeId) { - if (!homeId) - return self._mkdir(cb); - - return cb(homeId); - }); -}; - -module.exports = GoogleDrive; diff --git a/js/plugins/InsightStorage.js b/js/plugins/InsightStorage.js deleted file mode 100644 index ab4b791ce..000000000 --- a/js/plugins/InsightStorage.js +++ /dev/null @@ -1,287 +0,0 @@ -var request = require('request'); -var cryptoUtil = require('../util/crypto'); -var bitcore = require('bitcore'); -var buffers = require('buffer'); -var querystring = require('querystring'); -var Identity = require('../models/Identity'); -var log = require('../util/log'); - -var SEPARATOR = '|'; - -function InsightStorage(config) { - this.type = 'DB'; - this.storeUrl = config.url || 'https://insight.bitpay.com:443/api/email', - this.request = config.request || request; - - this.iterations = config.iterations || 1000; - this.salt = config.salt || 'jBbYTj8zTrOt6V'; -} - -InsightStorage.prototype.init = function() {}; - -InsightStorage.prototype.setCredentials = function(email, password, opts) { - this.email = email; - this.password = password; - this._cachedKey = null; -}; - -InsightStorage.prototype.createItem = function(name, value, callback) { - var self = this; - - this.getItem(name, function(err, retrieved) { - if (err || !retrieved) { - return self.setItem(name, value, callback); - } else { - return callback('EEXISTS'); - } - }); -}; - -InsightStorage.prototype.resendVerificationEmail = function (callback) { - var passphrase = this.getPassphrase(); - var authHeader = new buffers.Buffer(this.email + ':' + passphrase).toString('base64'); - var resendUrl = this.storeUrl + '/resend_email'; - - log.debug('Resending verification email: ' + this.email); - this.request.get({ - url: resendUrl, - headers: { - 'Authorization': authHeader - }, - body: null, - }, function(err, response, body) { - if (err) { - return callback('Connection error'); - } - if (response.statusCode === 409) { - return callback('BADCREDENTIALS: Invalid username or password'); - } else if (response.statusCode !== 200) { - return callback('Unable to process the request'); - } - return callback(); - }); -}; - -function mayBeOldPassword(password) { - // Test for base64 - return /^[a-zA-Z0-9\/=\+]+$/.test(password); -} - -InsightStorage.prototype.getItem = function(name, callback) { - var passphrase = this.getPassphrase(); - var self = this; - - this._makeGetRequest(passphrase, name, function(err, body, headers) { - if (err) log.warn(err); - if (err && err.indexOf('PNOTFOUND') !== -1 && mayBeOldPassword(self.password)) { - return self._brokenGetItem(name, callback); - } - return callback(err, body, headers); - }); -}; - -/* This key need to have DIFFERENT - * settings(salt,iterations) than the kdf for wallet/profile encryption - * in Encrpted*Storage. The user should be able - * to change the settings on config.js to modify salt / iterations - * for encryption, but - * mantain the same key & passphrase. This is why those settings are - * not shared with encryption - */ -InsightStorage.prototype.getKey = function() { - if (!this._cachedKey) { - this._cachedKey = cryptoUtil.kdf(this.password + SEPARATOR + this.email, this.salt, this.iterations); - } - return this._cachedKey; -}; - -InsightStorage.prototype.getPassphrase = function() { - return bitcore.util.twoSha256(this.getKey()).toString('base64'); -}; - -/** - * XmlHttpRequest's getAllResponseHeaders() method returns a string of response - * headers according to the format described here: - * http://www.w3.org/TR/XMLHttpRequest/#the-getallresponseheaders-method - * This method parses that string into a user-friendly key/value pair object. - */ -InsightStorage.parseResponseHeaders = function (headerStr) { - var headers = {}; - if (!headerStr) { - return headers; - } - var headerPairs = headerStr.split('\u000d\u000a'); - for (var i = 0, len = headerPairs.length; i < len; i++) { - var headerPair = headerPairs[i]; - var index = headerPair.indexOf('\u003a\u0020'); - if (index > 0) { - var key = headerPair.substring(0, index); - var val = headerPair.substring(index + 2); - headers[key] = val; - } - } - return headers; -} - -InsightStorage.prototype._makeGetRequest = function(passphrase, key, callback) { - var authHeader = new buffers.Buffer(this.email + ':' + passphrase).toString('base64'); - var retrieveUrl = this.storeUrl + '/retrieve'; - var getParams = { - url: retrieveUrl + '?' + querystring.encode({ - key: key, - rand: Math.random() // prevent cache - }), - headers: { - 'Authorization': authHeader - } - }; - this.request.get(getParams, - function(err, response, body) { - if (err) { - return callback('Connection error'); - } - if (response.statusCode === 403) { - return callback('PNOTFOUND: Profile not found'); - } - if (response.statusCode !== 200) { - return callback('Unable to read item from insight'); - } - return callback(null, body, InsightStorage.parseResponseHeaders(response.getAllResponseHeaders())); - } - ); -}; - -InsightStorage.prototype._brokenGetItem = function(name, callback) { - var passphrase = this._makeBrokenSecret(); - var self = this; - log.debug('using legacy get'); - this._makeGetRequest(passphrase, name, function(err, body) { - if (!err) { - return self._changePassphrase(function(err) { - if (err) { - return callback(err); - } - return callback(null, body); - }); - } - return callback(err); - }); -}; - -InsightStorage.prototype._makeBrokenSecret = function() { - var key = cryptoUtil.kdf(this.password + this.email, 'mjuBtGybi/4=', 100); - return cryptoUtil.kdf(key, this.password, 100); -}; - -InsightStorage.prototype._changePassphrase = function(callback) { - var passphrase = this._makeBrokenSecret(); - var newPassphrase = this.getPassphrase(); - var authHeader = new buffers.Buffer(this.email + ':' + passphrase).toString('base64'); - - var url = this.storeUrl + '/change_passphrase'; - this.request.post({ - url: url, - headers: { - 'Authorization': authHeader - }, - body: querystring.encode({ - newPassphrase: newPassphrase - }) - }, function(err, response, body) { - if (err) { - return callback('Connection error'); - } - if (response.statusCode === 409) { - return callback('BADCREDENTIALS: Invalid username or password'); - } - if (response.statusCode !== 200) { - return callback('Unable to store data on insight'); - } - return callback(); - }); -}; - -InsightStorage.prototype.setItem = function(name, value, callback) { - var passphrase = this.getPassphrase(); - var authHeader = new buffers.Buffer(this.email + ':' + passphrase).toString('base64'); - var registerUrl = this.storeUrl + '/save'; - - log.debug('setItem ' + name + ' size:'+ (value.length/1024).toFixed(1) + 'kb' ); - this.request.post({ - url: registerUrl, - headers: { - 'Authorization': authHeader - }, - body: querystring.encode({ - key: name, - record: value - }) - }, function(err, response, body) { - if (err) { - return callback('Connection error'); - } - if (response.statusCode === 409) { - return callback('BADCREDENTIALS: Invalid username or password'); - } else if (response.statusCode === 406) { - return callback('OVERQUOTA: Quota exceeded'); - } else if (response.statusCode === 501) { - return callback('EMAILERROR: Error sending verification email'); - } else if (response.statusCode !== 200) { - return callback('Unable to store data on insight'); - } - return callback(); - }); -}; - -InsightStorage.prototype.removeItem = function(key, callback) { - var passphrase = this.getPassphrase(); - var authHeader = new buffers.Buffer(this.email + ':' + passphrase).toString('base64'); - var deleteUrl = this.storeUrl + '/delete/item'; - var getParams = { - url: deleteUrl + '?' + querystring.encode({ - key: key - }), - headers: { - 'Authorization': authHeader - } - }; - log.debug('Erasing: ' + key); - this.request.get(getParams, function(err, response, body) { - if (err) { - return callback('Connection error'); - } - if (response.statusCode === 409) { - return callback('BADCREDENTIALS: Invalid username or password'); - } else if (response.statusCode !== 200) { - return callback('Unable to remove data on insight'); - } - return callback(); - }); -}; - -InsightStorage.prototype.clear = function(callback) { - var passphrase = this.getPassphrase(); - var authHeader = new buffers.Buffer(this.email + ':' + passphrase).toString('base64'); - var deleteUrl = this.storeUrl + '/delete/profile'; - - log.debug('Clearing storage for: ' + this.email); - this.request.post({ - url: deleteUrl, - headers: { - 'Authorization': authHeader - }, - body: null, - }, function(err, response, body) { - if (err) { - return callback('Connection error'); - } - if (response.statusCode === 409) { - return callback('BADCREDENTIALS: Invalid username or password'); - } else if (response.statusCode !== 200) { - return callback('Unable to remove data on insight'); - } - return callback(); - }); -}; - -module.exports = InsightStorage; diff --git a/js/plugins/LocalStorage.js b/js/plugins/LocalStorage.js deleted file mode 100644 index 3a7f5d82d..000000000 --- a/js/plugins/LocalStorage.js +++ /dev/null @@ -1,106 +0,0 @@ -'use strict'; -var _ = require('lodash'); -var preconditions = require('preconditions').singleton(); -var isChromeApp = typeof window !== "undefined" && window.chrome && chrome.runtime && chrome.runtime.id; - - -function LocalStorage(opts) { - this.type = 'DB'; - opts = opts || {}; - - - - - this.ls = opts.ls || - ((typeof localStorage !== "undefined") ? localStorage : null); - - if (isChromeApp && !this.ls) { - this.ls = localStorage = chrome.storage.local; - window.localStorage = chrome.storage.local; - } - - preconditions.checkState(this.ls, - 'localstorage not available, cannot run plugin'); -}; - -LocalStorage.prototype.init = function() {}; - -LocalStorage.prototype.setCredentials = function(email, password, opts) { - this.email = email; - this.password = password; -}; - -LocalStorage.prototype.getItem = function(k, cb) { - if (isChromeApp) { - chrome.storage.local.get(k, - function(data) { - //TODO check for errors - return cb(null, data[k]); - }); - } else { - return cb(null, this.ls.getItem(k)); - } -}; - -/** - * Same as setItem, but fails if an item already exists - */ -LocalStorage.prototype.createItem = function(name, value, callback) { - var self = this; - self.getItem(name, - function(err, data) { - if (data) { - return callback('EEXISTS'); - } else { - return self.setItem(name, value, callback); - } - }); -}; - -LocalStorage.prototype.setItem = function(k, v, cb) { - if (isChromeApp) { - var obj = {}; - obj[k] = v; - - chrome.storage.local.set(obj, cb); - } else { - this.ls.setItem(k, v); - return cb(); - } - -}; - -LocalStorage.prototype.removeItem = function(k, cb) { - if (isChromeApp) { - chrome.storage.local.remove(k, cb); - } else { - this.ls.removeItem(k); - return cb(); - } - -}; - -LocalStorage.prototype.clear = function(cb) { - // NOP - return cb(); -}; - -LocalStorage.prototype.allKeys = function(cb) { - if (isChromeApp) { - chrome.storage.local.get(null, function(items) { - return cb(null, _.keys(items)); - }); - } else { - var ret = []; - var l = this.ls.length; - - for (var i = 0; i < l; i++) - ret.push(this.ls.key(i)); - - return cb(null, ret); - } -}; - - - -module.exports = LocalStorage; diff --git a/js/services/applicationService.js b/js/services/applicationService.js deleted file mode 100644 index 5df3b85be..000000000 --- a/js/services/applicationService.js +++ /dev/null @@ -1,28 +0,0 @@ -'use strict'; -angular.module('copayApp.services') - .factory('applicationService', function($rootScope, $location, $timeout, go, isCordova) { - var root = {}; - var isChromeApp = window.chrome && chrome.runtime && chrome.runtime.id; - - root.restart = function() { - if (isCordova) { - $rootScope.iden = $rootScope.wallet = undefined; - go.path('/'); - $timeout(function(){ - $rootScope.$digest(); - },1); - - } else { - - // Go home reloading the application - var hashIndex = window.location.href.indexOf('#!/'); - if (isChromeApp) { - chrome.runtime.reload(); - } else { - window.location = window.location.href.substr(0, hashIndex); - } - } - }; - - return root; - }); diff --git a/js/services/backupService.js b/js/services/backupService.js deleted file mode 100644 index 5caf3f3ce..000000000 --- a/js/services/backupService.js +++ /dev/null @@ -1,86 +0,0 @@ -'use strict'; - -var BackupService = function($rootScope, notification) { - this.$rootScope = $rootScope; - this.notifications = notification; -}; - -BackupService.prototype.getCopayer = function(wallet) { - return wallet.totalCopayers > 1 ? wallet.getMyCopayerNickname() : ''; -}; - - - - - -BackupService.prototype._download = function(ew, walletName, filename) { - - var NewBlob = function(data, datatype) { - var out; - - try { - out = new Blob([data], { - type: datatype - }); - console.debug("case 1"); - } catch (e) { - window.BlobBuilder = window.BlobBuilder || - window.WebKitBlobBuilder || - window.MozBlobBuilder || - window.MSBlobBuilder; - - if (e.name == 'TypeError' && window.BlobBuilder) { - var bb = new BlobBuilder(); - bb.append(data); - out = bb.getBlob(datatype); - console.debug("case 2"); - } else if (e.name == "InvalidStateError") { - // InvalidStateError (tested on FF13 WinXP) - out = new Blob([data], { - type: datatype - }); - console.debug("case 3"); - } else { - // We're screwed, blob constructor unsupported entirely - console.debug("Errore"); - } - } - return out; - }; - - var blob; - - blob = new NewBlob(ew, 'text/plain;charset=utf-8'); - - - this.notifications.success('Backup created', 'Encrypted backup file saved'); - - // otherwise lean on the browser implementation - saveAs(blob, filename); -}; - -BackupService.prototype.walletEncrypted = function(wallet) { - return wallet.exportEncrypted(this.$rootScope.iden.password); -} - -BackupService.prototype.walletDownload = function(wallet) { - var ew = this.walletEncrypted(wallet); - var walletName = wallet.getName(); - var copayerName = this.getCopayer(wallet); - var filename = (copayerName ? copayerName + '-' : '') + walletName + '-keybackup.json.aes'; - this._download(ew, walletName, filename) -}; - -BackupService.prototype.profileEncrypted = function(iden) { - iden.setBackupNeeded(false); - return iden.exportEncryptedWithWalletInfo(iden.password); -} - -BackupService.prototype.profileDownload = function(iden) { - var ew = this.profileEncrypted(iden); - var name = iden.fullName; - var filename = name + '-profile.json'; - this._download(ew, name, filename) -}; - -angular.module('copayApp.services').service('backupService', BackupService); diff --git a/js/services/compatibility.js b/js/services/compatibility.js deleted file mode 100644 index 904ccc501..000000000 --- a/js/services/compatibility.js +++ /dev/null @@ -1,13 +0,0 @@ -'use strict'; - -angular.module('copayApp.services').factory('Compatibility', function($rootScope) { - var root = {}; - - root.check = function (scope) { - copay.Compatibility.listWalletsPre8(function(wallets) { - scope.anyWallet = wallets.length > 0 ? true : false; - scope.oldWallets = wallets; - }); - }; - return root; -}); diff --git a/js/services/configService.js b/js/services/configService.js deleted file mode 100644 index 55dae5068..000000000 --- a/js/services/configService.js +++ /dev/null @@ -1,36 +0,0 @@ -'use strict'; - -angular.module('copayApp.services').factory('configService', function($timeout, localstorageService, gettextCatalog, defaults) { - var root = {}; - - root.set = function(opts, cb) { - - // Options that have runtime effects - if (opts.logLevel) - copay.logger.setLevel(opts.logLevel); - - // Set current version - opts.version = copay.version; - - localstorageService.getItem('config', function(err, oldOpsStr) { - var oldOpts = {}; - try { - oldOpts = JSON.parse(oldOpsStr); - } catch (e) {}; - - var newOpts = {}; - _.extend(newOpts, copay.defaultConfig, oldOpts, opts); - - // TODO remove this global variable. - config = newOpts; - localstorageService.setItem('config', JSON.stringify(newOpts), cb); - }); - }; - - root.reset = function(cb) { - config = defaults; - localstorageService.removeItem('config', cb); - }; - - return root; -}); diff --git a/js/services/identityService.js b/js/services/identityService.js deleted file mode 100644 index 96cf0ed19..000000000 --- a/js/services/identityService.js +++ /dev/null @@ -1,363 +0,0 @@ -'use strict'; -angular.module('copayApp.services') - .factory('identityService', function($rootScope, $location, $timeout, $filter, pluginManager, notification, pendingTxsService, balanceService, applicationService, go) { - - // TODO: - // * remove iden from rootScope - // * remove wallet from rootScope - // * create walletService - - var root = {}; - root.check = function(scope) { - copay.Identity.checkIfExistsAny({ - pluginManager: pluginManager.getInstance(config), - }, function(anyProfile) { - copay.Wallet.checkIfExistsAny({ - pluginManager: pluginManager.getInstance(config), - }, function(anyWallet) { - scope.loading = false; - scope.anyProfile = anyProfile ? true : false; - scope.anyWallet = anyWallet ? true : false; - - if (!scope.anyProfile) { - $location.path('/createProfile'); - } - }); - }); - }; - - root.create = function(email, password, cb) { - copay.Identity.create({ - email: email, - password: password, - pluginManager: pluginManager.getInstance(config), - network: config.network, - networkName: config.networkName, - walletDefaults: config.wallet, - passphraseConfig: config.passphraseConfig, - failIfExists: true, - }, function(err, iden) { - if (err) return cb(err); - preconditions.checkState(iden); - root.bind(iden); - - return cb(null); - }); - }; - - root.createDefaultWallet = function(cb) { - var iden = $rootScope.iden; - - var walletOptions = { - nickname: iden.fullName, - networkName: config.networkName, - requiredCopayers: 1, - totalCopayers: 1, - password: iden.password, - name: 'My wallet', - }; - iden.createWallet(walletOptions, function(err, wallet) { - return cb(err); - }); - }; - - root.resendVerificationEmail = function(cb) { - var iden = $rootScope.iden; - iden.resendVerificationEmail(cb); - }; - - root.setServerStatus = function(headers) { - if (!headers) - return; - - var customHeaders = {}; - _.each(_.keys(headers), function(headerKey) { - var hk = headerKey.toLowerCase(); - if (hk.indexOf('x-') === 0) { - customHeaders[hk] = headers[headerKey]; - } - }); - - if (customHeaders['x-email-needs-validation']) - $rootScope.needsEmailConfirmation = true; - else - $rootScope.needsEmailConfirmation = null; - - if (customHeaders['x-quota-per-item']) - $rootScope.quotaPerItem = parseInt(customHeaders['x-quota-per-item']); - - if (customHeaders['x-quota-items-limit']) - $rootScope.quotaItems = parseInt(customHeaders['x-quota-items-limit']); - }; - - root.open = function(email, password, cb) { - var opts = { - email: email, - password: password, - pluginManager: pluginManager.getInstance(config), - network: config.network, - networkName: config.networkName, - walletDefaults: config.wallet, - passphraseConfig: config.passphraseConfig, - }; - - copay.Identity.open(opts, function(err, iden, headers) { - if (err) return cb(err); - root.setServerStatus(headers); - root.bind(iden); - return cb(null, iden); - }); - }; - - root.deleteProfile = function(cb) { - $rootScope.iden.remove(null, cb); - }; - - root.deleteWallet = function(w, cb) { - $rootScope.iden.deleteWallet(w.getId(), cb); - }; - - root.isFocused = function(wid) { - return $rootScope.wallet && wid === $rootScope.wallet.getId(); - }; - - root.setupGlobalVariables = function(iden) { - $rootScope.reconnecting = false; - $rootScope.iden = iden; - }; - - - root.noFocusedWallet = function() { - $rootScope.wallet = null; - $timeout(function() { - $rootScope.$digest(); - }) - }; - - root.setFocusedWallet = function(w, dontUpdateIt) { - if (!_.isObject(w)) - w = $rootScope.iden.getWalletById(w); - preconditions.checkState(w && _.isObject(w)); - - copay.logger.debug('Set focus:', w.getName()); - $rootScope.wallet = w; - - if (!dontUpdateIt) - $rootScope.iden.updateFocusedTimestamp(w.getId()); - - pendingTxsService.update(); - $timeout(function() { - $rootScope.$digest(); - }, 1); - }; - - root.notifyTxProposalEvent = function(w, e) { - if (e.cId == w.getMyCopayerId()) - return; - - var user = w.publicKeyRing.nicknameForCopayer(e.cId); - var name = w.getName(); - switch (e.type) { - case copay.Wallet.TX_NEW: - notification.info('[' + name + '] New Transaction', - $filter('translate')('You received a transaction proposal from') + ' ' + user); - break; - case copay.Wallet.TX_SIGNED: - notification.success('[' + name + '] Transaction Signed', - $filter('translate')('A transaction was signed by') + ' ' + user); - break; - case copay.Wallet.TX_BROADCASTED: - notification.success('[' + name + '] Transaction Approved', - $filter('translate')('A transaction was broadcasted by') + ' ' + user); - break; - case copay.Wallet.TX_REJECTED: - notification.warning('[' + name + '] Transaction Rejected', - $filter('translate')('A transaction was rejected by') + ' ' + user); - break; - } - }; - - root.installWalletHandlers = function(w) { - var wid = w.getId(); - w.on('connectionError', function() { - if (root.isFocused(wid)) { - var message = "Could not connect to the Insight server. Check your settings and network configuration"; - notification.error('Networking Error', message); - } - }); - - w.on('corrupt', function(peerId) { - copay.logger.warn('Received corrupt message from ' + peerId); - }); - - w.on('publicKeyRingUpdated', function() { - $rootScope.$digest(); - }); - - w.on('ready', function() { - var isFocused = root.isFocused(wid); - copay.logger.debug('Wallet:' + w.getName() + - ' is ready. Focused:', isFocused); - - balanceService.update(w, function() { - $rootScope.$digest(); - }, isFocused); - }); - - w.on('tx', function(address, isChange) { - if (!isChange) { - notification.funds('Funds received on ' + w.getName(), address); - } - balanceService.update(w, function() { - $rootScope.$digest(); - }, root.isFocused(wid)); - }); - - w.on('balanceUpdated', function() { - balanceService.update(w, function() { - $rootScope.$digest(); - }, root.isFocused(wid)); - }); - - w.on('insightReconnected', function() { - $rootScope.reconnecting = false; - balanceService.update(w, function() { - $rootScope.$digest(); - }, root.isFocused(wid)); - }); - - w.on('insightError', function() { - if (root.isFocused(wid)) { - $rootScope.reconnecting = true; - $rootScope.$digest(); - } - }); - w.on('newAddresses', function() { - // Nothing yet - }); - - // Disabled for now, does not seens to have much value for the user - // w.on('paymentACK', function(memo) { - // notification.success('Payment Acknowledged', memo); - // }); - - w.on('txProposalEvent', function(ev) { - - if (root.isFocused(wid)) { - pendingTxsService.update(); - } - - // TODO aqui lo unico que cambia son los locked - // se puede optimizar - balanceService.update(w, function() { - $rootScope.$digest(); - }, root.isFocused(wid)); - - root.notifyTxProposalEvent(w, ev); - $timeout(function() { - $rootScope.$digest(); - }); - }); - - w.on('addressBookUpdated', function(dontDigest) { - if (root.isFocused(wid)) { - if (!dontDigest) { - $rootScope.$digest(); - } - } - }); - w.on('connect', function(peerID) { - $rootScope.$digest(); - }); - // TODO? - // w.on('close', ); - // w.on('locked',); - }; - - root.bind = function(iden) { - preconditions.checkArgument(_.isObject(iden)); - copay.logger.debug('Binding profile...'); - - var self = this; - root.setupGlobalVariables(iden); - iden.on('newWallet', function(wid) { - var w = iden.getWalletById(wid); - copay.logger.debug('newWallet:', - w.getName(), wid, iden.getLastFocusedWalletId()); - root.installWalletHandlers(w); - if (wid == iden.getLastFocusedWalletId()) { - copay.logger.debug('GOT Focused wallet:', w.getName()); - root.setFocusedWallet(w, true); - go.walletHome(); - } - - // At the end (after all handlers are in place)...start the wallet. - w.netStart(); - }); - - iden.on('noWallets', function() { - notification.warning('No Wallets', 'Your profile has no wallets. Create one here'); - $rootScope.starting = false; - $location.path('/add'); - $timeout(function() { - $rootScope.$digest(); - }, 1); - }); - - iden.on('walletDeleted', function(wid) { - // do nothing. this is handled 'on sync' on controller. - }); - - iden.on('walletStorageError', function(wid, message) { - notification.error('Error storing wallet', message); - }); - - iden.on('closed', function() { - delete $rootScope['wallet']; - delete $rootScope['iden']; - applicationService.restart(); - }); - }; - - root.signout = function() { - if ($rootScope.iden) { - $rootScope.signingOut = true; - $rootScope.iden.close(function() { // Will trigger 'closed' - $timeout(function() { - $rootScope.signingOut = null; - }, 100); - }); // Will trigger 'closed' - } - }; - - root.createWallet = function(opts, cb) { - $rootScope.iden.createWallet(opts, cb); - }; - - root.importWallet = function(encryptedObj, pass, opts, cb) { - copay.Compatibility.importEncryptedWallet($rootScope.iden, encryptedObj, pass, opts, cb); - }; - - root.joinWallet = function(opts, cb) { - $rootScope.iden.joinWallet(opts, function(err, w) { - return cb(err); - }); - }; - - root.importProfile = function(str, password, cb) { - copay.Identity.importFromEncryptedFullJson(str, password, { - pluginManager: pluginManager.getInstance(config), - network: config.network, - networkName: config.networkName, - walletDefaults: config.wallet, - passphraseConfig: config.passphraseConfig, - }, function(err, iden, walletObjs) { - if (err) return cb(err); - root.bind(iden); - iden.importMultipleWalletsFromObj(walletObjs); - return cb(); - }); - }; - - return root; - }); diff --git a/js/services/localstorageService.js b/js/services/localstorageService.js deleted file mode 100644 index 1dc19d04b..000000000 --- a/js/services/localstorageService.js +++ /dev/null @@ -1,9 +0,0 @@ - -'use strict'; - -angular.module('copayApp.services') - .factory('localstorageService', function($rootScope) { - var LS = require('../js/plugins/LocalStorage'); - var ls = new LS(); - return ls; - }); diff --git a/js/services/pendingTxsService.js b/js/services/pendingTxsService.js deleted file mode 100644 index c523392c0..000000000 --- a/js/services/pendingTxsService.js +++ /dev/null @@ -1,51 +0,0 @@ -'use strict'; - -angular.module('copayApp.services') - .factory('pendingTxsService', function($rootScope, $filter, rateService) { - var root = {}; - - root.setAlternativeAmount = function(w, tx, cb) { - var alternativeIsoCode = w.settings.alternativeIsoCode; - rateService.whenAvailable(function() { - _.each(tx.outs, function(out) { - var valueSat = out.valueSat * w.settings.unitToSatoshi; - out.alternativeAmount = $filter('noFractionNumber')( - rateService.toFiat(valueSat, alternativeIsoCode), 2); - out.alternativeIsoCode = alternativeIsoCode; - }); - if (cb) return cb(tx); - }); - }; - - root.getDecoratedTxProposals = function(w) { - var txps = w.getPendingTxProposals(); - - _.each(txps, function(tx) { - root.setAlternativeAmount(w, tx); - if (tx.outs) { - _.each(tx.outs, function(out) { - out.valueSat = out.value; - out.value = $filter('noFractionNumber')(out.value); - }); - } - }); - return txps; - }; - - /** - * @desc adds 2 fields to wallet: pendingTxProposalsCountForUs, pendingTxProposals. - * - * @param w wallet - */ - root.update = function(w) { - var w = $rootScope.wallet; - if (!w) return; - - //pendingTxCount - var ret = w.getPendingTxProposalsCount(); - w.pendingTxProposalsCountForUs = ret.pendingForUs; - w.pendingTxProposals = root.getDecoratedTxProposals(w); - }; - - return root; - }); diff --git a/js/services/pinService.js b/js/services/pinService.js deleted file mode 100644 index 90bacf55a..000000000 --- a/js/services/pinService.js +++ /dev/null @@ -1,115 +0,0 @@ -'use strict'; - -angular.module('copayApp.services') - .factory('pinService', function($rootScope, $timeout, localstorageService) { - - var KEY = 'pinDATA'; - var SALT = '4gllotIKguqi0EkIslC0'; - var ITER = 5000; - - var ls = localstorageService; - var root = {}; - - var _firstpin; - - root.check = function(cb) { - ls.getItem(KEY, function(err, value) { - return cb(err, value ? true : false); - }); - }; - - root.get = function(pin, cb) { - ls.getItem(KEY, function(err, value) { - if (!value) return cb(null); - var enc = value; - var data = copay.crypto.decrypt('' + parseInt(pin), enc); - var err = new Error('Could not decrypt'); - if (data) { - var obj; - try { - obj = JSON.parse(data); - err = null; - } catch (e) {}; - } - return cb(err, obj); - }); - }; - - root.save = function(pin, email, password, cb) { - var credentials = { - email: email, - password: password, - }; - var enc = copay.crypto.encrypt('' + parseInt(pin), credentials, SALT, ITER); - ls.setItem(KEY, enc, function(err) { - return cb(err); - }); - }; - - root.clear = function(cb) { - ls.removeItem(KEY, cb); - }; - - root.clearPin = function(scope) { - scope.digits = []; - scope.defined = []; - }; - - root.pressPin = function(scope, digit, skipOpenWithPin) { - scope.error = null; - scope.digits.push(digit); - scope.defined.push(true); - if (scope.digits.length == 4) { - var pin = scope.digits.join(''); - if (!$rootScope.hasPin) { - if (!_firstpin) { - _firstpin = pin; - scope.askForPin = 2; - $timeout(function() { - scope.clear(); - }, 100); - return; - } - else { - if (pin === _firstpin) { - _firstpin = null; - scope.askForPin = null; - scope.createPin(pin); - return; - } - else { - _firstpin = null; - scope.askForPin = 1; - $timeout(function() { - scope.clear(); - scope.error = 'Entered PINs were not equal. Try again'; - $timeout(function() { - scope.error = null; - }, 2000); - }, 100); - return; - } - } - } - if (!skipOpenWithPin) { - scope.openWithPin(pin); - } - } - }; - - root.skipPin = function(scope, creatingProfile) { - if (!$rootScope.hasPin) { - if (!creatingProfile) { - scope.openWallets(); - } - else { - scope.createDefaultWallet() - } - } - else { - scope.pinLogout(); - } - }; - - return root; - }); diff --git a/js/services/rate.js b/js/services/rate.js deleted file mode 100644 index 64dd100d4..000000000 --- a/js/services/rate.js +++ /dev/null @@ -1,8 +0,0 @@ -'use strict'; - -angular.module('copayApp.services').factory('rateService', function(request) { - var cfg = _.extend(config.rates, { - request: request - }); - return copay.RateService.singleton(cfg); -}); diff --git a/js/services/request.js b/js/services/request.js deleted file mode 100644 index 5a92093c9..000000000 --- a/js/services/request.js +++ /dev/null @@ -1,6 +0,0 @@ -'use strict'; - -angular.module('copayApp.services').factory('request', function() { - return require('request'); -}); - diff --git a/js/services/txStatus.js b/js/services/txStatus.js deleted file mode 100644 index 2bfcaf31d..000000000 --- a/js/services/txStatus.js +++ /dev/null @@ -1,36 +0,0 @@ -'use strict'; - -angular.module('copayApp.services').factory('txStatus', function($modal) { - var root = {}; - - root.notify = function(status) { - var msg; - if (status == copay.Wallet.TX_BROADCASTED) - msg = 'Transaction broadcasted'; - else if (status == copay.Wallet.TX_PROPOSAL_SENT) - msg = 'Transaction proposal created'; - else if (status == copay.Wallet.TX_SIGNED) - msg = 'Transaction proposal signed'; - else if (status == copay.Wallet.TX_REJECTED) - msg = 'Transaction was rejected'; - - if (msg) - root.openModal(msg); - }; - - root.openModal = function(statusStr) { - var ModalInstanceCtrl = function($scope, $modalInstance) { - $scope.statusStr = statusStr; - $scope.cancel = function() { - $modalInstance.dismiss('cancel'); - }; - }; - $modal.open({ - templateUrl: 'views/modals/tx-status.html', - windowClass: 'tiny', - controller: ModalInstanceCtrl, - }); - }; - - return root; -}); diff --git a/js/shell.js b/js/shell.js deleted file mode 100644 index 9e3a89ff0..000000000 --- a/js/shell.js +++ /dev/null @@ -1,122 +0,0 @@ -/* - ** copay-shell integration - */ -(function() { - /* - ** This is a monkey patch for when Copay is running from - ** within Copay-Shell (atom-shell). Since the renderer (the frontend) - ** receives context from Node.js, we get a `module.exports` contruct - ** available to us. Because of this, some libs (specifically Moment.js) - ** attempt to assume their CommonJS form and bind to this. This causes - ** there to be no references in the window to these libs, so let's trick - ** the renderer into thinking that we are _not_ in a CommonJS environment. - */ - if (typeof module !== 'undefined') module = { - exports: false - }; - - // are we running in copay shell? - if (window.process && process.type === 'renderer') { - window.cshell = initCopayShellBindings(); - } else { - return; - } - - function controller(name) { - return angular.element( - document.querySelectorAll( - '[ng-controller="' + name + '"], [data-ng-controller="' + name + '"]' - ) - ).scope(); - }; - - function needsWalletLogin(ipc) { - ipc.send('alert', 'info', 'Please select a wallet.'); - }; - - function initCopayShellBindings() { - - var ipc = require('ipc'); - var clipb = require('clipboard'); - - // atom shell forces to implement the clipboard (on osx) on our own - thanks obama. - - Mousetrap.stopCallback = function() { - return false - }; - - Mousetrap.bind('command+c', function(e) { - clipb.writeText(window.getSelection().toString()); - }); - - Mousetrap.bind('command+v', function(e) { - if (document.activeElement) { - document.activeElement.value = clipb.readText(); - document.activeElement.dispatchEvent(new Event('change')); - } - }); - - // handle messages - ipc.on('address:create', function(data) { - location.href = '#/addresses'; - var ctrl = controller('AddressesController'); - if (!ctrl) return needsWalletLogin(ipc); - ctrl.newAddr(); - }); - - ipc.on('transactions:send', function(data) { - location.href = '#/send'; - var ctrl = controller('SendController'); - if (!ctrl) return needsWalletLogin(ipc); - }); - - ipc.on('transactions:all', function(data) { - location.href = '#/transactions'; - var ctrl = controller('TransactionsController'); - if (!ctrl) return needsWalletLogin(ipc); - ctrl.show(); - }); - - ipc.on('transactions:pending', function(data) { - location.href = '#/transactions'; - var ctrl = controller('TransactionsController'); - if (!ctrl) return needsWalletLogin(ipc); - ctrl.show(true); - }); - - ipc.on('backup:download', function(data) { - location.href = '#/backup'; - var ctrl = controller('BackupController'); - if (!ctrl) return needsWalletLogin(ipc); - ctrl.download(); - }); - - ipc.on('backup:email', function(data) { - location.href = '#/backup'; - var ctrl = controller('BackupController'); - if (!ctrl) return needsWalletLogin(ipc); - ctrl.openModal(); - }); - - ipc.on('backup:import:data', function(data) { - location.href = '#/import'; - var ctrl = controller('ImportController'); - if (!ctrl) return; - ctrl.backupText = data; - ctrl.openPasteArea(); - ctrl.$apply(); - }); - - ipc.on('backup:import', function(data) { - location.href = '#/import'; - }); - - // let the shell know when an error occurs - window.onerror = function(err) { - ipc.send('error', err); - }; - - return ipc; - }; - -})(); diff --git a/js/util/HTTP.js b/js/util/HTTP.js deleted file mode 100644 index 0c184fb8a..000000000 --- a/js/util/HTTP.js +++ /dev/null @@ -1,79 +0,0 @@ -var preconditions = require('preconditions').singleton(); - -module.exports = { - request: function(options, callback) { - preconditions.checkArgument(_.isObject(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) { - console.trace(err); - throw err; - } - }; - - var method = (options.method || 'GET').toUpperCase(); - var url = options.url; - var req = options; - - req.headers = req.headers || {}; - req.body = req.body || req.data || ''; - - var xhr = options.xhr || new XMLHttpRequest(); - xhr.open(method, url, 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); - }); - - if (req.responseType) { - xhr.responseType = req.responseType; - } - - 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) { - var status; - if (xhr.status === 0 || !xhr.statusText) { - status = 'HTTP Request Error: This endpoint likely does not support cross-origin requests.'; - } else { - status = xhr.statusText; - } - return ret._error(null, status, null, options); - }; - - if (req.body) { - xhr.send(req.body); - } else { - xhr.send(null); - } - - return ret; - }, -}; diff --git a/js/util/crypto.js b/js/util/crypto.js deleted file mode 100644 index 88c821363..000000000 --- a/js/util/crypto.js +++ /dev/null @@ -1,83 +0,0 @@ -/** - * Small module for some helpers that wrap sjcl with some good practices. - */ -var sjcl = require('sjcl'); -var _ = require('lodash'); - -var log = require('../util/log.js'); -var config = require('../../config'); - -var defaultSalt = (config && config.passphraseConfig && config.passphraseConfig.storageSalt) || 'mjuBtGybi/4='; -var defaultIterations = (config && config.passphraseConfig && config.passphraseConfig.iterations) || 1000; - -module.exports = { - - /** - * @param {string} password - * @param {string} salt - base64 encoded, defaults to 'mjuBtGybi/4=' - * @param {number} iterations - defaults to 100 - * @param {number} length - bits, defaults to 512 bits - * @returns {string} base64 encoded pbkdf2 derivation using sha1 for hmac - */ - kdf: function(password, salt, iterations, length) { - return sjcl.codec.base64.fromBits( - this.kdfbinary(password, salt, iterations, length) - ); - }, - - /** - * @param {string} key - * @param {string} data - * @return {string} base64 encoded hmac - */ - hmac: function(key, data) { - return sjcl.codec.base64.fromBits( - new sjcl.misc.hmac(key, sjcl.hash.sha256).encrypt(data) - ); - }, - - /** - * @param {string} password - * @param {string} salt - base64 encoded, defaults to 'mjuBtGybi/4=' - * @param {number} iterations - defaults to 100 - * @param {number} length - bits, defaults to 512 bits - * @returns {string} base64 encoded pbkdf2 derivation using sha1 for hmac - */ - kdfbinary: function(password, salt, iterations, length) { - iterations = iterations || defaultIterations; - length = length || 512; - salt = sjcl.codec.base64.toBits(salt || defaultSalt); - - var hash = sjcl.hash.sha256.hash(sjcl.hash.sha256.hash(password)); - var prff = function(key) { - return new sjcl.misc.hmac(hash, sjcl.hash.sha1); - }; - - return sjcl.misc.pbkdf2(hash, salt, iterations, length, prff); - }, - - /** - * Encrypts symmetrically using a passphrase - */ - encrypt: function(key, message, salt, iter) { - if (!_.isString(message)) { - message = JSON.stringify(message); - } - sjcl.json.defaults.salt = salt || defaultSalt; - sjcl.json.defaults.iter = iter || defaultIterations; - return sjcl.encrypt(key, message); - }, - - /** - * Decrypts symmetrically using a passphrase - */ - decrypt: function(key, sjclEncryptedJson) { - var output = {}; - try { - return sjcl.decrypt(key, sjclEncryptedJson); - } catch (e) { - log.debug('Decryption failed due to error: ' + e.message); - return null; - } - } -}; diff --git a/js/util/csv.js b/js/util/csv.js deleted file mode 100644 index b573ea816..000000000 --- a/js/util/csv.js +++ /dev/null @@ -1,95 +0,0 @@ -/** - * Small module for exporting data to CSV. - */ -var _ = require('lodash'); -var preconditions = require('preconditions').singleton(); -var moment = require('moment'); - -var logger = require('../util/log.js'); -var config = require('../../config'); - - -var COL_DELIMITER = ','; -var ROW_DELIMITER = '\r\n'; - -function getValue(obj, property) { - if (_.isFunction(property)) { - try { - return property(obj); - } catch (err) { - if (_.isString(err)) return err; - return undefined; - } - } - if (!_.isObject(obj)) return undefined; - return obj.hasOwnProperty(property) ? obj[property] : undefined; -}; - -function formatValue(value, type, format) { - if (_.isUndefined(value) || _.isNull(value)) return ''; - - var r; - switch (type) { - default: - case 'string': - r = value.toString(); - r.replace('"', '\\"'); - break; - case 'date': - r = moment(value).format(format); - break; - case 'number': - r = value.toString(); - break; - } - - // escape when commas in values - if (r.indexOf(',') !== -1) { - r = '"' + r + '"'; - } - return r; -}; - -function getHeader(descriptor) { - return _.map(descriptor.columns, function (col) { - return col.label || (_.isString(col.property) ? col.property : '') || ''; - }); -}; - -function processDataRow(data, descriptor) { - return _.map(descriptor.columns, function (col) { - var value = getValue(data, col.property); - var formatted = formatValue(value, col.type, col.format); - return formatted; - }); -}; - -/** - * @desc Convert json object to csv based on a descriptor - * - * @param {array} data - the array of json objects to convert to csv - * @param {object} descriptor - an object that parameterizes the conversion - * @param {function} cb - called with the resulting csv - */ -module.exports.toCsv = function(data, descriptor, cb) { - preconditions.shouldBeArray(data); - preconditions.shouldBeObject(descriptor); - preconditions.shouldBeArray(descriptor.columns); - preconditions.shouldBeFunction(cb); - - var colDelimiter = descriptor.colDelimiter || COL_DELIMITER; - var rowDelimiter = descriptor.rowDelimiter || ROW_DELIMITER; - - var rows = _.map(data, function (dataRow) { - return processDataRow(dataRow, descriptor); - }); - - var header = getHeader(descriptor); - rows.unshift(header); - - var csv = _.reduce(rows, function (memo, row) { - return memo + row.join(colDelimiter) + rowDelimiter; - }, ''); - - return cb(null, csv); -}; diff --git a/js/util/log.js b/js/util/log.js deleted file mode 100644 index c0e7a13a9..000000000 --- a/js/util/log.js +++ /dev/null @@ -1,150 +0,0 @@ -var config = config || require('../../config'); -var _ = require('lodash'); -var ls; - -try { - var LS = require('../js/plugins/LocalStorage'); - ls = new LS(); -} catch (e) {}; - -/** - * @desc - * A simple logger that wraps the console.log methods when available. - * - * Usage: - *
- *   log = new Logger('copay');
- *   log.setLevel('info');
- *   log.debug('Message!'); // won't show
- *   log.setLevel('debug');
- *   log.debug('Message!', 1); // will show '[debug] copay: Message!, 1'
- * 
- * - * @param {string} name - a name for the logger. This will show up on every log call - * @constructor - */ -var Logger = function(name) { - this.name = name || 'log'; - this.level = 2; -}; - -Logger.prototype.getLevels = function() { - return levels; -}; - - -var levels = { - 'debug': 0, - 'info': 1, - 'log': 2, - 'warn': 3, - 'error': 4, - 'fatal': 5 -}; - -_.each(levels, function(level, levelName) { - Logger.prototype[levelName] = function() { - if (level >= levels[this.level]) { - - if (Error.stackTraceLimit && this.level == 'debug') { - var old = Error.stackTraceLimit; - Error.stackTraceLimit = 2; - var stack; - - // this hack is to be compatible with IE11 - try { - anerror(); - } catch (e) { - stack = e.stack; - } - var lines = stack.split('\n'); - var caller = lines[2]; - caller = ':' + caller.substr(6); - Error.stackTraceLimit = old; - } - - var str = '[' + levelName + (caller || '') + '] ' + arguments[0], - extraArgs, - extraArgs = [].slice.call(arguments, 1); - if (console[levelName]) { - extraArgs.unshift(str); - console[levelName].apply(console, extraArgs); - } else { - if (extraArgs.length) { - str += JSON.stringify(extraArgs); - } - console.log(str); - } - } - }; -}); - -/** - * @desc - * Sets the level of a logger. A level can be any bewteen: 'debug', 'info', 'log', - * 'warn', 'error', and 'fatal'. That order matters: if a logger's level is set to - * 'warn', calling level.debug won't have any effect. - * - * @param {number} level - the name of the logging level - */ -Logger.prototype.setLevel = function(level) { - this.level = level; -}; - -/** - * @class Logger - * @method debug - * @desc Log messages at the debug level. - * @param {*} args - the arguments to be logged. - */ -/** - * @class Logger - * @method info - * @desc Log messages at the info level. - * @param {*} args - the arguments to be logged. - */ -/** - * @class Logger - * @method log - * @desc Log messages at an intermediary level called 'log'. - * @param {*} args - the arguments to be logged. - */ -/** - * @class Logger - * @method warn - * @desc Log messages at the warn level. - * @param {*} args - the arguments to be logged. - */ -/** - * @class Logger - * @method error - * @desc Log messages at the error level. - * @param {*} args - the arguments to be logged. - */ -/** - * @class Logger - * @method fatal - * @desc Log messages at the fatal level. - * @param {*} args - the arguments to be logged. - */ - -var logger = new Logger('copay'); -var error = new Error(); - -var logLevel = config.logLevel || 'info'; - - - -if (ls && ls.getItem) { - ls.getItem("config", function(err, value) { - if (err) return; - var localConfig = JSON.parse(value); - if (localConfig && localConfig.logLevel) - logLevel = localConfig.logLevel; - logger.setLevel(logLevel); - }); -} else { - logger.setLevel(logLevel); -} - -module.exports = logger; diff --git a/karma.conf.js b/karma.conf.js deleted file mode 100644 index 0390efd96..000000000 --- a/karma.conf.js +++ /dev/null @@ -1,118 +0,0 @@ -// Karma configuration -// Generated on Fri Mar 21 2014 17:52:41 GMT-0300 (ART) - -module.exports = function(config) { - config.set({ - - // base path that will be used to resolve all patterns (eg. files, exclude) - basePath: '', - - - // frameworks to use - // available frameworks: https://npmjs.org/browse/keyword/karma-adapter - frameworks: ['mocha', 'chai'], - - - // list of files / patterns to load in the browser - files: [ - //Configs - 'config.js', - - 'lib/angular/angular.min.js', - 'lib/angular-mocks/angular-mocks.js', - 'lib/moment/moment.js', - 'lib/ng-idle/angular-idle.min.js', - 'lib/angular-moment/angular-moment.js', - 'lib/qrcode-generator/js/qrcode.js', - 'lib/angular-qrcode/qrcode.js', - 'lib/angular-route/angular-route.min.js', - 'lib/angular-foundation/mm-foundation.min.js', - 'lib/angular-foundation/mm-foundation-tpls.min.js', - 'lib/angular-load/angular-load.min.js', - 'lib/angular-gravatar/build/md5.min.js', - 'lib/angular-gravatar/build/angular-gravatar.min.js', - 'lib/angular-touch/angular-touch.min.js', - 'lib/angular-gettext/dist/angular-gettext.min.js', - 'lib/inherits/inherits.js', - 'lib/lodash/dist/lodash.js', - 'lib/file-saver/FileSaver.js', - 'lib/socket.io-client/socket.io.js', - 'lib/sjcl.js', - 'lib/qrcode-decoder-js/lib/qrcode-decoder.min.js', - - 'lib/bitcore.js', - 'js/copayBundle.js', - - //App-specific Code - 'js/app.js', - 'js/routes.js', - 'js/services/*.js', - 'js/directives.js', - 'js/filters.js', - 'js/controllers/*.js', - 'js/translations.js', - 'js/init.js', - - 'test/mocks/FakeBlockchainSocket.js', - - 'test/mocha.conf.js', - - //test files - 'setup/karma.js', - 'test/unit/**/*.js', - 'test/*.js', - ], - - - // list of files to exclude - exclude: [], - - - // preprocess matching files before serving them to the browser - // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor - preprocessors: { - 'js/controllers/*.js': ['coverage'] - }, - - coverageReporter: { - type: 'html', - dir: 'coverage/' - }, - - - // test results reporter to use - // possible values: 'dots', 'progress' - // available reporters: https://npmjs.org/browse/keyword/karma-reporter - reporters: ['progress', 'coverage'], - - - // web server port - port: 9876, - - - // enable / disable colors in the output (reporters and logs) - colors: true, - - - // level of logging - // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG - logLevel: config.LOG_INFO, - - - // enable / disable watching file and executing tests whenever any file changes - autoWatch: true, - - - // start these browsers - // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher - browsers: ['Chrome', 'Firefox'], - - - // Continuous Integration mode - // if true, Karma captures browsers, runs the tests and exits - singleRun: false, - - // if browser doesn't capture output in given timeout (ms), kill it - captureTimeout: 60000 - }); -}; diff --git a/launch.js b/launch.js deleted file mode 100755 index 5b9d9c2ab..000000000 --- a/launch.js +++ /dev/null @@ -1,34 +0,0 @@ -#! /usr/bin/node - -'use strict'; - - - -var sys = require('sys') -var exec = require('child_process').exec; - -function puts(error, stdout, stderr) { - sys.puts(stdout) -} - -function isNumber(n) { - return !isNaN(parseInt(n)) && isFinite(n); -} - -var args = process.argv.slice(2); -var n_str = args[0]; -if (!isNumber(n_str)) { - console.log('Program requires one numeric argument for the amount of copayers'); - process.exit(1); -} - -var N = parseInt(n_str); -var DEFAULT_PORT = process.env.DEFAULT_PORT || 3000; - - -for (var i = 0; i < N; i++) { - var port = (i + DEFAULT_PORT); - console.log('Simulating copayer #' + (i + 1) + ' at http://localhost:' + port); - var command = 'PORT=' + port + ' npm start &' - exec(command, puts); -} diff --git a/lib/bitcore.js b/lib/bitcore.js deleted file mode 100644 index be555e03b..000000000 --- a/lib/bitcore.js +++ /dev/null @@ -1,636 +0,0 @@ -require=(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;oi;i++)n[i]^=r[i];var o=t.fromUncompressedPubKey(e);o=t.multiply(o,n);var u=o.toUncompressedPubKey();return u},r.prototype.next=function(){var e=this.generatePubKey();return new r(this.chaincode,e)},r.fromMasterPublicKey=function(e){var n=e.substr(0,130),t=e.substr(130,e.length);return new r(t,n)},r.decodeSeed=function(t){for(var i=t.trim().split("\n"),o=[],c=0;ct)return!0;if(t>n)return!1;var o=r.slice(4,8).readUInt32BE(0),i=e.slice(4,8).readUInt32BE(0);return o>i?!0:!1},a._encrypt=function(r,e,t,o){var i=n.encrypt(r,e,t,o);return i},a._decrypt=function(r,e){var t=n.decrypt(r,e);return t},a._sign=function(r,n){var t=e.sign(n,r);return t},a._verify=function(r,n,t){var o=e.verifyWithPubKey(r,t,n);return o},module.exports=a}).call(this,require("buffer").Buffer); -},{"./ECIES":"0Qraa1","./Key":"ALJ4PS","./Message":"CBDCgz","buffer":111,"preconditions":180}],"./lib/AuthMessage":[function(require,module,exports){ -module.exports=require('cBnJMk'); -},{}],"Rpcuro":[function(require,module,exports){ -"use strict";var URL=require("url"),Address=require("./Address"),BIP21=function(t){if(this.data={},this.address=void 0,"string"==typeof t)this.parse(t);else if("object"==typeof t)this.fromObj(t);else if("undefined"!=typeof t)throw new Error("Invalid argument")};BIP21.prototype.fromObj=function(t){for(var r in t)this.data[r]=t[r];t.address&&(delete this.data.address,this.setAddress(t.address))},BIP21.prototype.parse=function(t){var r=URL.parse(t,!0);if("bitcoin:"!=r.protocol)throw new Error("Invalid protocol");var e=/[^:]*:\/?\/?([^?]*)/.exec(t);this.setAddress(e&&e[1]);for(var s in r.query){var d=r.query[s];"amount"===s&&(d=Number(d)),"r"===s&&(this.data.merchant=d),this.data[s]=d}},BIP21.prototype.isValid=function(t){var r=t||[],e=!0;"undefined"!=typeof this.data.amount&&(e&=!isNaN(this.data.amount)),this.address&&(e&="object"==typeof this.address&&this.address.isValid()),e&=!(!this.address&&!this.data.r);for(var s in this.data)0==s.indexOf("req-")&&(e&=-1!=r.indexOf(s));return!!e},BIP21.prototype.setAddress=function(t){return t&&(this.address=Address.validate(t)?new Address(t):t),this},BIP21.prototype.getURI=function(){return URL.format({protocol:"bitcoin:",host:this.address,query:this.data})},module.exports=BIP21; -},{"./Address":"G+CcXD","url":142}],"./lib/BIP21":[function(require,module,exports){ -module.exports=require('Rpcuro'); -},{}],"./lib/BIP39":[function(require,module,exports){ -module.exports=require('82LilS'); -},{}],"82LilS":[function(require,module,exports){ -(function(e){var n=require("../util"),r=require("./sjcl"),t=require("./SecureRandom"),i=function(e){var n=new r.misc.hmac(e,r.hash.sha512);this.encrypt=function(){return n.encrypt.apply(n,arguments)}},o=function(e,n,t){var o=r.misc.pbkdf2(e,n,t,512,i);return r.codec.hex.fromBits(o)},l=function(){};l.mnemonic=function(e,n){if(n||(n=128),n%32!=0)throw new Error("bits must be multiple of 32");var r=t.getRandomBuffer(n/8);return l.entropy2mnemonic(e,r)},l.entropy2mnemonic=function(e,r){for(var t=n.sha256(r),i="",o=8*r.length,l=0;lc)return!1;o+=("00000000000"+c.toString(2)).slice(-11)}if(o.length%11!=0)throw new Error("internal error - entropy not an even multiple of 11 bits - "+o.length);for(var u=o.length/33,s=o.slice(-u),a=o.slice(0,o.length-u),f=new e(a.length/8),l=0;l0){var i=new e(t);return i.fill(0),t==r.length?i:(n=n.toBuffer(),e.concat([i,n],t+n.length))}return n.toBuffer()}},s={encode:function(r){var t=new e(r.length+4),o=n(r);return r.copy(t),o.copy(t,r.length),l.encode(t)},decode:function(e){var r=l.decode(e);if(r.length<4)throw new Error("invalid input: too short");var t=r.slice(0,-4),o=r.slice(-4),i=n(t),c=i.slice(0,4);if(o.toString("hex")!==c.toString("hex"))throw new Error("checksum mismatch");return t}};exports.setBuffer=function(e){i=e},exports.base58=l,exports.base58Check=s,exports.encode=l.encode,exports.decode=l.decode}).call(this,require("buffer").Buffer); -},{"bignum":63,"buffer":111,"crypto":115}],"./lib/Block":[function(require,module,exports){ -module.exports=require('pJEQEB'); -},{}],"pJEQEB":[function(require,module,exports){ -(function(t){function e(t){"object"!=typeof t&&(t={}),this.hash=t.hash||null,this.prev_hash=t.prev_hash||r.NULL_HASH,this.merkle_root=t.merkle_root||r.NULL_HASH,this.timestamp=t.timestamp||0,this.bits=t.bits||0,this.nonce=t.nonce||0,this.version=t.version||0,this.height=t.height||0,this.size=t.size||0,this.active=t.active||!1,this.chainWork=t.chainWork||r.EMPTY_BUFFER,this.txs=t.txs||[]}var r=require("../util"),i=require("./Script"),o=require("bignum"),h=require("buffertools"),s=require("./Transaction"),n=s.In,a=s.Out,c=s.COINBASE_OP,u=require("../util/error").VerificationError,l={maxTimeOffset:7200,largestHash:new o("10000000000000000000000000000000000000000000000000000000000000000",16)};e.prototype.getHeader=function(){var e=new t(80),r=0;return e.writeUInt32LE(this.version,r),r+=4,this.prev_hash.copy(e,r),r+=32,this.merkle_root.copy(e,r),r+=32,e.writeUInt32LE(this.timestamp,r),r+=4,e.writeUInt32LE(this.bits,r),r+=4,e.writeUInt32LE(this.nonce,r),r+=4,e},e.prototype.parse=function(t,e){if(this.version=t.word32le(),this.prev_hash=t.buffer(32),this.merkle_root=t.buffer(32),this.timestamp=t.word32le(),this.bits=t.word32le(),this.nonce=t.word32le(),this.txs=[],this.size=0,!e)for(var r=t.varInt(),i=0;r>i;i++){var o=new s;o.parse(t),this.txs.push(o)}},e.prototype.calcHash=function(){var t=this.getHeader();return r.twoSha256(t)},e.prototype.checkHash=function(){return this.hash&&this.hash.length?0==h.compare(this.calcHash(),this.hash):!1},e.prototype.getHash=function(){return this.hash&&this.hash.length||(this.hash=this.calcHash()),this.hash},e.prototype.checkProofOfWork=function(){var t=r.decodeDiffBits(this.bits),e=h.reverse(this.hash);if(h.compare(e,t)>0)throw new u("Difficulty target not met");return!0},e.prototype.getWork=function(){var t=r.decodeDiffBits(this.bits,!0);return l.largestHash.div(t.add(1))},e.prototype.checkTimestamp=function(){var t=(new Date).getTime()/1e3;if(this.timestamp>t+l.maxTimeOffset)throw new u("Timestamp too far into the future");return!0},e.prototype.checkTransactions=function(t){if(!Array.isArray(t)||t.length<=0)throw new u("No transactions");if(!t[0].isCoinBase())throw new u("First tx must be coinbase");for(var e=1;e1;h=Math.floor((h+1)/2)){for(var n=0;h>n;n+=2){var a=Math.min(n+1,h-1),c=i[o+n],u=i[o+a];i.push(r.twoSha256(t.concat([c,u])))}o+=h}return i},e.prototype.calcMerkleRoot=function(t){var e=this.getMerkleTree(t);return e[e.length-1]},e.prototype.checkMerkleRoot=function(e){if(!this.merkle_root||!this.merkle_root.length)throw new u("No merkle root");if(0!==h.compare(this.calcMerkleRoot(e),new t(this.merkle_root)))throw new u("Merkle root incorrect");return!0},e.prototype.checkBlock=function(t){if(!this.checkHash())throw new u("Block hash invalid");if(this.checkProofOfWork(),this.checkTimestamp(),t&&(this.checkTransactions(t),!this.checkMerkleRoot(t)))throw new u("Merkle hash invalid");return!0},e.getBlockValue=function(t){var e=50*r.COIN;return e/=Math.pow(2,Math.floor(t/21e4)),e=Math.floor(e),e=new o(e)},e.prototype.getBlockValue=function(){return e.getBlockValue(this.height)},e.prototype.toString=function(){return""},e.prototype.createCoinbaseTx=function(t){var e=new s;return e.ins.push(new n({s:r.EMPTY_BUFFER,q:4294967295,o:c})),e.outs.push(new a({v:r.bigIntToValue(this.getBlockValue()),s:i.createPubKeyOut(t).getBuffer()})),e},e.prototype.solve=function(t,e){var i=this.getHeader(),o=r.decodeDiffBits(this.bits);t.solve(i,o,e)},e.prototype.getStandardizedObject=function(t){var e={hash:r.formatHashFull(this.getHash()),version:this.version,prev_block:r.formatHashFull(this.prev_hash),mrkl_root:r.formatHashFull(this.merkle_root),time:this.timestamp,bits:this.bits,nonce:this.nonce,height:this.height};if(t){var i=this.getMerkleTree(t).map(function(t){return r.formatHashFull(t)});e.mrkl_root=i[i.length-1],e.n_tx=t.length;var o=80;o+=r.getVarIntSize(t.length),t=t.map(function(t){return t=t.getStandardizedObject(),o+=t.size,t}),e.size=o,e.tx=t,e.mrkl_tree=i}else e.size=this.size;return e},module.exports=e}).call(this,require("buffer").Buffer); -},{"../util":207,"../util/error":206,"./Script":"hQ0t76","./Transaction":"LJhYtm","bignum":63,"buffer":111,"buffertools":"fugeBw"}],"./lib/Bloom":[function(require,module,exports){ -module.exports=require('KifRG4'); -},{}],"KifRG4":[function(require,module,exports){ -function Bloom(){this.data="",this.hashFuncs=0}function ROTL32(t,n){return t<>32-n}function getBlockU32(t,n){var o=4*t,s=n[o+0]<<0|n[o+1]<<8|n[o+2]<<16|n[o+3]<<24;return s}function toInt(t){return~~t}function min(t,n){return n>t?t:n}var MAX_BLOOM_FILTER_SIZE=36e3,MAX_HASH_FUNCS=50,LN2SQUARED=.48045301391820144,LN2=.6931471805599453,bit_mask=[1,2,4,8,16,32,64,128];Bloom.prototype.hash=function(t,n){for(var o=t*(4294967295/(this.hashFuncs-1)),s=3432918353,a=461845907,h=n.length/4,i=-h;i;i++){var r=getBlockU32(i);r*=s,r=ROTLF32(r,15),r*=a,o^=r,o=ROTFL(o,13),o=5*o+3864292196}var e=n.slice(4*h),r=0;switch(3&n.length){case 3:r^=e[2]<<16;case 2:r^=e[1]<<8;case 1:r^=e[0],r*=s,r=ROTL32(r,15),r*=a,o^=r}return o^=n.length,o^=o>>16,o*=2246822507,o^=o>>13,o*=3266489909,o^=o>>16,o%(8*this.data.length)},Bloom.prototype.insert=function(t){for(var n=0;n>3]|=bit_mask[7&o]}},Bloom.prototype.contains=function(t){for(var n=0;n>3]&bit_mask[7&o]))return!1}return!0},Bloom.prototype.sizeOk=function(){return this.data.length<=MAX_BLOOM_FILTER_SIZE&&this.hashFuncs<=MAX_HASH_FUNCS},Bloom.prototype.init=function(t,n){var o=min(toInt(-1/LN2SQUARED*t*Math.log(n)),8*MAX_BLOOM_FILTER_SIZE)/8;this.data[o]=0,this.hashFuncs=min(toInt(8*this.data.length/t*LN2),MAX_HASH_FUNCS)},module.exports=Bloom; -},{}],"./lib/Connection":[function(require,module,exports){ -module.exports=require('DB/p3X'); -},{}],"DB/p3X":[function(require,module,exports){ -(function(e){function t(e,t,r){if(this.config=r||a,this.network=h[this.config.network]||h.livenet,this.socket=e,this.peer=t,this.config.proxy){var s=require("socks5-client");this.socket=new s(this.config.proxy.host,this.config.proxy.port)}this.active=!1,this.recvVer=0,this.sendVer=0,this.bestHeight=0,this.inbound=!!this.socket.server,this.getaddr=!1,this.buffers=new o,(new Date).getTime()>1329696e6&&(this.recvVer=209,this.sendVer=209),this.setupHandlers()}var r=require("../util/log"),s=1e7,n=7e4,i=require("bufferput"),o=require("buffers");require("../patches/Buffers.monkey").patch(o);var a=require("../config"),h=require("../networks"),c=require("./Block"),d=require("./Transaction"),f=require("../util"),u=require("../util/BinaryParser"),p=require("buffertools"),g=f.twoSha256,l=require("./SecureRandom"),v=l.getPseudoRandomBuffer(8),b=require("util"),k=require("events").EventEmitter,m=6e4;b.inherits(t,k),t.prototype.open=function(e){return"function"==typeof e&&this.once("connect",e),this.socket.connect(this.peer.port,this.peer.host),this},t.prototype.setupHandlers=function(){this.socket.addListener("connect",this.handleConnect.bind(this)),this.socket.addListener("error",this.handleError.bind(this)),this.socket.addListener("end",this.handleDisconnect.bind(this)),this.socket.addListener("data",function(e){var t=35;r.debug("["+this.peer+"] Recieved "+e.length+" bytes of data:"),r.debug("... "+p.toHex(e.slice(0,t>e.length?e.length:t))+(e.length>t?"...":""))}.bind(this)),this.socket.addListener("data",this.handleData.bind(this))},t.prototype.handleConnect=function(){this.inbound||this.sendVersion(),this.emit("connect",{conn:this,socket:this.socket,peer:this.peer})},t.prototype.handleError=function(e){110==e.errno||"ETIMEDOUT"==e.errno?r.info("connection timed out for "+this.peer):111==e.errno||"ECONNREFUSED"==e.errno?r.info("connection refused for "+this.peer):r.warn("connection with "+this.peer+" "+e.toString()),this.emit("error",{conn:this,socket:this.socket,peer:this.peer,err:e})},t.prototype.handleDisconnect=function(){this.emit("disconnect",{conn:this,socket:this.socket,peer:this.peer})},t.prototype.handleMessage=function(t){if(t){try{switch(t.command){case"version":if(0===p.compare(v,t.nonce))return void this.socket.end();this.inbound&&this.sendVersion(),t.version>=209&&this.sendMessage("verack",new e([])),this.sendVer=Math.min(t.version,n),t.version<209?this.recvVer=Math.min(t.version,n):this.once("verack",function(){this.recvVer=t.version}.bind(this)),this.bestHeight=t.start_height;break;case"verack":this.recvVer=Math.min(t.version,n),this.active=!0;break;case"ping":"object"==typeof t.nonce&&this.sendPong(t.nonce)}}catch(s){return void r.err('Error while handling "'+t.command+'" message from '+this.peer+":\n"+(s.stack?s.stack:s.toString()))}this.emit(t.command,{conn:this,socket:this.socket,peer:this.peer,message:t})}},t.prototype.sendPong=function(e){this.sendMessage("pong",e)},t.prototype.sendVersion=function(){var t="/BitcoinX:0.1/",r=new i;r.word32le(n),r.word64le(1),r.word64le(Math.round((new Date).getTime()/1e3)),r.pad(26),r.pad(26),r.put(v),r.varint(t.length),r.put(new e(t,"ascii")),r.word32le(0),this.sendMessage("version",r.buffer())},t.prototype.sendGetBlocks=function(t,r,s){r=r||f.NULL_HASH;var n=new i;n.word32le(this.sendVer),n.varint(t.length);for(var o=0;o12)throw"Command name too long";var a;a=this.sendVer>=209?g(s).slice(0,4):new e([]);var h=new i;h.put(n),h.put(o),h.pad(12-o.length),h.word32le(s.length),h.put(a),h.put(s);var c=h.buffer();r.debug("["+this.peer+"] Sending message "+t+" ("+s.length+" bytes)"),this.socket.write(c)}catch(d){r.err("Error while sending message to peer "+this.peer+": "+(d.stack?d.stack:d.toString()))}},t.prototype.handleData=function(e){return this.buffers.push(e),this.buffers.length>s?(r.err("Peer "+this.peer+" exceeded maxreceivebuffer, disconnecting."+(err.stack?err.stack:err.toString())),void this.socket.destroy()):void this.processData()},t.prototype.processData=function(){if(!(this.buffers.length<20)){for(var e=this.network.magic,t=0;;){if(this.buffers.get(t)===e[0]&&this.buffers.get(t+1)===e[1]&&this.buffers.get(t+2)===e[2]&&this.buffers.get(t+3)===e[3]){0!==t&&(r.debug("["+this.peer+"] Received "+t+" bytes of inter-message garbage: "),r.debug("... "+this.buffers.slice(0,t)),this.buffers.skip(t));break}if(t>this.buffers.length-4)return void this.buffers.skip(t);t++}var s=this.buffers.get(16)+(this.buffers.get(17)<<8)+(this.buffers.get(18)<<16)+(this.buffers.get(19)<<24),n=this.recvVer>=209?24:20,i=n+s;if(!(this.buffers.length=209?this.buffers.slice(20,24):null;if(r.debug("["+this.peer+"] Received message "+o+" ("+s+" bytes)"),null!==h){var c=g(a).slice(0,4);if(0!==p.compare(c,h))return void r.err("["+this.peer+"] Checksum failed",{cmd:o,expected:c.toString("hex"),actual:h.toString("hex")})}var d;try{d=this.parseMessage(o,a)}catch(f){r.err("Error while parsing message "+o+" from "+this.peer+":\n"+(f.stack?f.stack:f.toString()))}d&&this.handleMessage(d),this.buffers.skip(i),this.processData()}}},t.prototype.parseMessage=function(e,t){var s,n=new u(t),i={command:e};switch(e){case"version":i.version=n.word32le(),i.services=n.word64le(),i.timestamp=n.word64le(),i.addr_me=n.buffer(26),i.addr_you=n.buffer(26),i.nonce=n.buffer(8),i.subversion=n.varStr(),i.start_height=n.word32le();break;case"inv":case"getdata":for(i.count=n.varInt(),i.invs=[],s=0;ss;s++)i.starts.push(n.buffer(32));i.stop=n.buffer(32);break;case"addr":var p=n.varInt();for(p>1e3&&(p=1e3),i.addrs=[],s=0;p>s;s++)i.addrs.push({time:n.word32le(),services:n.word64le(),ip:n.buffer(16),port:n.word16be()});break;case"alert":i.payload=n.varStr(),i.signature=n.varStr();break;case"ping":this.recvVer>m&&(i.nonce=n.buffer(8));break;case"getaddr":case"verack":case"reject":break;default:return r.err("Connection.parseMessage(): Command not implemented",{cmd:e}),null}return i},module.exports=t}).call(this,require("buffer").Buffer); -},{"../config":"4itQ50","../networks":"ULNIu2","../patches/Buffers.monkey":"kytKTK","../util":207,"../util/BinaryParser":"b3ZSD7","../util/log":"AdF7pF","./Block":"pJEQEB","./SecureRandom":"p4SiC2","./Transaction":"LJhYtm","buffer":111,"bufferput":"aXRuS6","buffers":"OBo3aV","buffertools":"fugeBw","events":"T9Wsc/","socks5-client":189,"util":144}],"ez/meX":[function(require,module,exports){ -exports.intFromCompact=function(r){var t=(r>>>24&255)>>>0,n=(16777215&r)<<8*(t-3)>>>0;return n}; -},{}],"./lib/Deserialize":[function(require,module,exports){ -module.exports=require('ez/meX'); -},{}],"./lib/Electrum":[function(require,module,exports){ -module.exports=require('hdzBvq'); -},{}],"hdzBvq":[function(require,module,exports){ -(function(e){function r(r){this.mpk=new e(r,"hex")}var t=require("./Key"),u=require("./Point"),n=require("../util").twoSha256,i=(require("buffertools"),require("bignum"));r.prototype.getSequence=function(r,t){var u=r?1:0,o=e.concat([new e(t+":"+u+":","utf8"),this.mpk]);return i.fromBuffer(n(o))},r.prototype.generatePubKey=function(r,n){var o=i.fromBuffer(this.mpk.slice(0,32),{size:32}),f=i.fromBuffer(this.mpk.slice(32,64),{size:32}),c=new u(o,f),p=this.getSequence(n,r),s=new t;s.private=p.toBuffer(),s.regenerateSync(),s.compressed=!1;var a=u.fromUncompressedPubKey(s.public);pt=u.add(c,a);var m=pt.x.toBuffer({size:32}),b=pt.y.toBuffer({size:32}),h=new e([4]),l=new t;return l.compressed=!1,l.public=e.concat([h,m,b]),l.public},r.prototype.generateChangePubKey=function(e){return this.generatePubKey(e,!0)},module.exports=r}).call(this,require("buffer").Buffer); -},{"../util":207,"./Key":"ALJ4PS","./Point":"6tXgqr","bignum":63,"buffer":111,"buffertools":"fugeBw"}],"x1O6JW":[function(require,module,exports){ -(function(e){function i(e,i){if(e.lengthn;n++)t*=256,t+=e[n];return t}function t(e){return i(e,1)}function n(e){return i(e,4)}var r=require("./Base58").base58,s=require("../util"),h=require("./Key"),a=require("./Point"),c=require("./SecureRandom"),o=require("bignum"),d=require("../networks"),l=new o("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141",16),u=(new o("79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798",16),function(i){if("undefined"==typeof i||"mainnet"==i||"livenet"==i?(i="livenet",this.version=d.livenet.hkeyPrivateVersion):"testnet"==i&&(this.version=d.testnet.hkeyPrivateVersion),"livenet"==i||"testnet"==i)return this.depth=0,this.parentFingerprint=new e([0,0,0,0]),this.childIndex=new e([0,0,0,0]),this.chainCode=c.getRandomBuffer(32),this.eckey=h.generateSync(),this.hasPrivateKey=!0,this.pubKeyHash=s.sha256ripe160(this.eckey.public),this.buildExtendedPublicKey(),void this.buildExtendedPrivateKey();if("string"==typeof i){var t=r.decode(i);if(82!=t.length)throw new Error("Not enough data, expected 82 and received "+t.length);var n=t.slice(78,82);i=t.slice(0,78);var a=s.sha256(s.sha256(i));if(a[0]!=n[0]||a[1]!=n[1]||a[2]!=n[2]||a[3]!=n[3])throw new Error("Invalid checksum")}void 0!==i&&null!==i&&this.initFromBytes(i)});u.seed=function(i,t){if(t||(t="livenet"),e.isBuffer(i)||(i=new e(i,"hex")),i.length<16)return!1;if(i.length>64)return!1;var n=s.sha512hmac(i,new e("Bitcoin seed")),r=new u(null);return r.depth=0,r.parentFingerprint=new e([0,0,0,0]),r.childIndex=new e([0,0,0,0]),r.chainCode=n.slice(32,64),r.version=d[t].hkeyPrivateVersion,r.eckey=new h,r.eckey.private=n.slice(0,32),r.eckey.regenerateSync(),r.hasPrivateKey=!0,r.pubKeyHash=s.sha256ripe160(r.eckey.public),r.buildExtendedPublicKey(),r.buildExtendedPrivateKey(),r},u.prototype.initFromBytes=function(e){if(78!=e.length)throw new Error("not enough data");this.version=n(e.slice(0,4)),this.depth=t(e.slice(4,5)),this.parentFingerprint=e.slice(5,9),this.childIndex=n(e.slice(9,13)),this.chainCode=e.slice(13,45);var i=e.slice(45,78),r=this.version==d.livenet.hkeyPrivateVersion||this.version==d.testnet.hkeyPrivateVersion,a=this.version==d.livenet.hkeyPublicVersion||this.version==d.testnet.hkeyPublicVersion;if(r&&0==i[0])this.eckey=new h,this.eckey.private=i.slice(1,33),this.eckey.compressed=!0,this.eckey.regenerateSync(),this.pubKeyHash=s.sha256ripe160(this.eckey.public),this.hasPrivateKey=!0;else{if(!a||2!=i[0]&&3!=i[0])throw new Error("Invalid key");this.eckey=new h,this.eckey.public=i,this.pubKeyHash=s.sha256ripe160(this.eckey.public),this.hasPrivateKey=!1}this.buildExtendedPublicKey(),this.buildExtendedPrivateKey()},u.prototype.buildExtendedPublicKey=function(){this.extendedPublicKey=new e([]);var i=null;switch(this.version){case d.livenet.hkeyPublicVersion:case d.livenet.hkeyPrivateVersion:i=d.livenet.hkeyPublicVersion;break;case d.testnet.hkeyPublicVersion:case d.testnet.hkeyPrivateVersion:i=d.testnet.hkeyPublicVersion;break;default:throw new Error("Unknown version")}this.extendedPublicKey=e.concat([new e([i>>24]),new e([i>>16&255]),new e([i>>8&255]),new e([255&i]),new e([this.depth]),this.parentFingerprint,new e([this.childIndex>>>24]),new e([this.childIndex>>>16&255]),new e([this.childIndex>>>8&255]),new e([255&this.childIndex]),this.chainCode,this.eckey.public])},u.prototype.extendedPublicKeyString=function(i){if(void 0===i||"base58"===i){var t=s.sha256(s.sha256(this.extendedPublicKey)),n=t.slice(0,4),h=e.concat([this.extendedPublicKey,n]);return r.encode(h)}if("hex"===i)return this.extendedPublicKey.toString("hex");throw new Error("bad format")},u.prototype.buildExtendedPrivateKey=function(){if(this.hasPrivateKey){this.extendedPrivateKey=new e([]);var i=this.version;this.extendedPrivateKey=e.concat([new e([i>>24]),new e([i>>16&255]),new e([i>>8&255]),new e([255&i]),new e([this.depth]),this.parentFingerprint,new e([this.childIndex>>>24]),new e([this.childIndex>>>16&255]),new e([this.childIndex>>>8&255]),new e([255&this.childIndex]),this.chainCode,new e([0]),this.eckey.private])}},u.prototype.extendedPrivateKeyString=function(i){if(void 0===i||"base58"===i){var t=s.sha256(s.sha256(this.extendedPrivateKey)),n=t.slice(0,4),h=e.concat([this.extendedPrivateKey,n]);return r.encode(h)}if("hex"===i)return this.extendedPrivateKey.toString("hex");throw new Error("bad format")},u.prototype.derive=function(e){var i=e.split("/");if("m"==e||"M"==e||"m'"==e||"M'"==e)return this;var t=this;for(var n in i){var r=i[n];if(0!=n){var s=r.length>1&&"'"==r[r.length-1],h=2147483647&parseInt(s?r.slice(0,r.length-1):r);s&&(h+=2147483648),t=t.deriveChild(h)}else if("m"!=r)throw new Error("invalid path")}return t},u.prototype.deriveChild=function(i){var t=[];t.push(i>>24&255),t.push(i>>16&255),t.push(i>>8&255),t.push(255&i),t=new e(t);var n=0!=(2147483648&i),r=this.version==d.livenet.hkeyPrivateVersion||this.version==d.testnet.hkeyPrivateVersion;if(n&&(!this.hasPrivateKey||!r))throw new Error("Cannot do private key derivation without private key");var c=null;if(this.hasPrivateKey){var y=null;y=e.concat(n?[new e([0]),this.eckey.private,t]:[this.eckey.public,t]);var v=s.sha512hmac(y,this.chainCode),p=o.fromBuffer(v.slice(0,32),{size:32}),w=v.slice(32,64),b=o.fromBuffer(this.eckey.private,{size:32}),f=p.add(b).mod(l);c=new u(null),c.chainCode=w,c.eckey=new h,c.eckey.private=f.toBuffer({size:32}),c.eckey.regenerateSync(),c.hasPrivateKey=!0}else{var y=e.concat([this.eckey.public,t]),v=s.sha512hmac(y,this.chainCode),p=v.slice(0,32),w=v.slice(32,64),k=new h;k.private=p,k.regenerateSync(),k.compressed=!1;var P=a.fromUncompressedPubKey(k.public),F=new h;F.public=this.eckey.public,F.compressed=!1;var x=a.fromUncompressedPubKey(F.public),K=a.add(P,x).toUncompressedPubKey();c=new u(null),c.chainCode=new e(w);var g=new h;g.public=K,g.compressed=!0,c.eckey=g,c.hasPrivateKey=!1}return c.childIndex=i,c.parentFingerprint=this.pubKeyHash.slice(0,4),c.version=this.version,c.depth=this.depth+1,c.eckey.compressed=!0,c.pubKeyHash=s.sha256ripe160(c.eckey.public),c.buildExtendedPublicKey(),c.buildExtendedPrivateKey(),c},module.exports=u}).call(this,require("buffer").Buffer); -},{"../networks":"ULNIu2","../util":207,"./Base58":"6VqyzY","./Key":"ALJ4PS","./Point":"6tXgqr","./SecureRandom":"p4SiC2","bignum":63,"buffer":111}],"./lib/HierarchicalKey":[function(require,module,exports){ -module.exports=require('x1O6JW'); -},{}],"CBDCgz":[function(require,module,exports){ -(function(e){"use strict";var r=require("../util"),i=require("./Key"),n=require("bignum"),r=require("../util"),t=function(){};t.sign=function(e,r){var i=t.magicHash(e),n=r.signSync(i);return n},t.verifyWithPubKey=function(e,r,n){var u=t.magicHash(r),a=new i;return 65==e.length&&(a.compressed=!1),a.public=e,a.verifySignatureSync(u,n)},t.signMessage=function(e,r){var u=t.magicHash(e),a=n.fromBuffer(r.private),s=i.signCompressed(u,a);return s},t.verifyMessage=function(e,r,n){var u=t.magicHash(r);return i.verifyCompressed(u,n,e)},t.magicBytes=new e("Bitcoin Signed Message:\n"),t.magicHash=function(i){var n=t.magicBytes,u=r.varIntBuf(n.length),a=new e(i),s=r.varIntBuf(a.length),c=e.concat([u,n,s,a]),f=r.twoSha256(c);return f},module.exports=t}).call(this,require("buffer").Buffer); -},{"../util":207,"./Key":"ALJ4PS","bignum":63,"buffer":111}],"./lib/Message":[function(require,module,exports){ -module.exports=require('CBDCgz'); -},{}],"qYkfjX":[function(require,module,exports){ -var log=require("../util/log"),networks=require("../networks"),Address=require("./Address"),Peer=require("./Peer"),PeerManager=require("./PeerManager"),util=require("util"),EventEmitter=require("events").EventEmitter,preconditions=require("preconditions").singleton(),NetworkMonitor=function(e){preconditions.checkArgument(e),this.peerman=e,this.networkName=e.config.network,this.init()};util.inherits(NetworkMonitor,EventEmitter),NetworkMonitor.create=function(e){this.config=e;var t=new PeerManager({network:e.networkName});return t.addPeer(new Peer(e.host,e.port)),new NetworkMonitor(t)},NetworkMonitor.prototype.init=function(){var e=this,t=function(e){var t=e.message.invs;e.conn.sendGetData(t)},n=function(t){e.emit("block",t.message)},o=function(t){var n=t.message.tx;e.emit("tx",n);for(var o=n.getSendingAddresses(e.networkName),r=0;r=31402||this.peers.length<1e3)&&(e.conn.sendGetAddr(),e.conn.getaddr=!0)},PeerManager.prototype.handleReady=function(e){log.info("connected to "+e.conn.peer.host+":"+e.conn.peer.port),this.emit("connect",{pm:this,conn:e.conn,socket:e.socket,peer:e.peer}),0==this.isConnected&&(this.emit("netConnected",e),this.isConnected=!0)},PeerManager.prototype.handleAddr=function(e){if(this.peerDiscovery){var n=GetAdjustedTime();e.message.addrs.forEach(function(e){try{(e.time<=1e8||e.time>n+600)&&(e.time=n-432e3);var t=new Peer(e.ip,e.port,e.services);t.lastSeen=e.time,this.peers.push(t)}catch(r){log.warn("Invalid addr received: "+r.message)}}.bind(this)),e.message.addrs.length<1e3&&(e.conn.getaddr=!1)}},PeerManager.prototype.handleGetAddr=function(){},PeerManager.prototype.handleError=function(e){log.err("unkown error with peer "+e.peer+" (disconnecting): "+e.err),this.handleDisconnect.apply(this,[].slice.call(arguments))},PeerManager.prototype.handleDisconnect=function(e){log.info("disconnected from peer "+e.peer);var n=this.connections.indexOf(e.conn);-1!=n&&this.connections.splice(n,1),this.removePeer(e.peer),this.pool.length&&(log.info("replacing peer using the pool of "+this.pool.length+" seeds"),this.addPeer(this.pool.pop())),this.connections.length||(this.emit("netDisconnected"),this.isConnected=!1)},PeerManager.prototype.getActiveConnection=function(){var e=this.connections.filter(function(e){return e.active});if(e.length){var n=Math.floor(Math.random()*e.length),t=e[n];return t.socket.writable?t:(e.splice(n,1),this.getActiveConnection())}return null},PeerManager.prototype.getActiveConnections=function(){return this.connections.slice(0)},PeerManager.prototype.discover=function(e,n){var t=this,r=networks[t.config.network].dnsSeeds;t.limit=e.limit||12;var i=r.map(function(e){return function(n){return~t.seeds.resolved.indexOf(e)?n(null,t.seeds.results[e]):~t.seeds.failed.indexOf(e)?n(null,[]):(log.info("resolving dns seed "+e),void dns.resolve(e,function(r,i){return r?(log.err("failed to resolve dns seed "+e,r),t.seeds.failed.push(e),n(null,[])):(log.info("found "+i.length+" peers from "+e),t.seeds.resolved.push(e),i=i.map(function(e){return new Peer(e,networks[t.config.network].defaultClientPort)}),i.forEach(function(e){t.peers.length33&&!this.compressed()||34==this.data.length&&1!=this.data[33]||this.data.length>34)throw new Error("invalid data length")}),"undefined"==typeof this.network())throw new Error("invalid network")},i.prototype.payload=function(t){if(t)return this.doAsBinary(function(){t.copy(this.data,1)}),t;var i=this.as("binary");return 34==i.length?i.slice(1,33):33==i.length?i.slice(1):void 0},i.prototype.compressed=function(i){if(void 0===i){var e=34,r=this.as("binary");if(r.length==e&&1==r[e-1])return!0;if(r.length==e-1)return!1;throw new Error("invalid private key")}this.doAsBinary(function(){var e=34;if(i){var r=new t(e);this.data.copy(r),this.data=r,this.data[e-1]=1}else this.data=this.data.slice(0,e-1)})},i.prototype.network=function(){var t,i=this.version(),e=n.livenet,r=n.testnet;return i===e.privKeyVersion?t=e:i===r.privKeyVersion&&(t=r),t},module.exports=i}).call(this,require("buffer").Buffer); -},{"../networks":"ULNIu2","../util/EncodedData":"eLfUFE","../util/VersionedData":"QLzNQg","buffer":111,"util":144}],"./lib/PrivateKey":[function(require,module,exports){ -module.exports=require('izTl9z'); -},{}],"7siE1N":[function(require,module,exports){ -(function(t){function e(t){t=t||{},this.host=t.host||"127.0.0.1",this.port=t.port||8332,this.user=t.user||"user",this.pass=t.pass||"pass",this.protocol="http"==t.protocol?o:n,this.batchedCalls=null,this.disableAgent=t.disableAgent||!1,this.rejectUnauthorized=t.rejectUnauthorized||!1}function r(t,e,r){function s(t,e){return function(){var s=arguments.length-1;if(this.batchedCalls)var s=arguments.length;for(var o=0;s>o;o++)e[o]&&(arguments[o]=e[o](arguments[o]));this.batchedCalls?this.batchedCalls.push({jsonrpc:"2.0",method:t,params:l(arguments)}):r.call(this,{method:t,params:l(arguments,0,arguments.length-1)},arguments[arguments.length-1])}}var o={str:function(t){return t.toString()},"int":function(t){return parseFloat(t)},"float":function(t){return parseFloat(t)},bool:function(t){return t===!0||"1"==t||"true"==t||"true"==t.toString().toLowerCase()}};for(var n in e)if(e.hasOwnProperty(n)){for(var i=e[n].split(" "),a=0;a=h.map.OP_1&&t<=h.map.OP_16}function n(t){return t=t?2:65535>=t?3:5}function s(e){var r=void 0;return e=e?(r=new t(2),r.writeUInt8(h.map.OP_PUSHDATA1,0),r.writeUInt8(e,1)):65535>=e?(r=new t(3),r.writeUInt8(h.map.OP_PUSHDATA2,0),r.writeUInt16LE(e,1)):(r=new t(5),r.writeUInt8(h.map.OP_PUSHDATA4,0),r.writeUInt32LE(e,1)),r}var u=(require("../config"),require("../util/log")),h=require("./Opcode"),o=require("buffertools"),f=require("../util/util"),c=require("../util/BinaryParser"),p=require("bufferput"),a=0,l=1,g=2,k=3,y=4,S=5,P=["unknown","pubkey","pubkeyhash","multisig","scripthash","return"];e.TX_UNKNOWN=a,e.TX_PUBKEY=l,e.TX_PUBKEYHASH=g,e.TX_MULTISIG=k,e.TX_SCRIPTHASH=y,e.TX_RETURN=S,e.prototype.parse=function(){this.chunks=[];for(var t=new c(this.buffer);!t.eof();){var e,r,n=t.word8();n>0&&nh.map.OP_16)return!1}return!0},e.prototype.isP2SH=function(){return 3==this.chunks.length&&this.chunks[0]==h.map.OP_HASH160&&t.isBuffer(this.chunks[1])&&20==this.chunks[1].length&&this.chunks[2]==h.map.OP_EQUAL},e.prototype.isPubkey=function(){return 2==this.chunks.length&&t.isBuffer(this.chunks[0])&&this.chunks[1]==h.map.OP_CHECKSIG},e.prototype.isReturn=function(){return 2==this.chunks.length&&t.isBuffer(this.chunks[1])&&this.chunks[0]==h.map.OP_RETURN},e.prototype.isPubkeyHash=function(){return 5==this.chunks.length&&this.chunks[0]==h.map.OP_DUP&&this.chunks[1]==h.map.OP_HASH160&&t.isBuffer(this.chunks[2])&&20==this.chunks[2].length&&this.chunks[3]==h.map.OP_EQUALVERIFY&&this.chunks[4]==h.map.OP_CHECKSIG},e.prototype.isMultiSig=function(){return this.chunks.length>3&&r(this.chunks[0])&&this.chunks.slice(1,this.chunks.length-2).every(function(e){return t.isBuffer(e)})&&r(this.chunks[this.chunks.length-2])&&this.chunks[this.chunks.length-1]==h.map.OP_CHECKMULTISIG},e.prototype.isPubkeyHashScriptSig=function(){return 2==this.chunks.length&&t.isBuffer(this.chunks[0])&&t.isBuffer(this.chunks[1])},e.prototype.isP2shScriptSig=function(){if(!r(this.chunks[0])||0!==this.chunks[0])return!1;var t=new e(this.chunks[this.chunks.length-1]),n=t.classify();return n!==a},e.prototype.isMultiSigScriptSig=function(){return r(this.chunks[0])&&0===this.chunks[0]?!this.isP2shScriptSig():!1},e.prototype.isPubkeyScriptSig=function(){return 1==this.chunks.length&&t.isBuffer(this.chunks[0])},e.prototype.countSignatures=function(){var t=0,e=this.chunks.length;return t=this.isMultiSigScriptSig()?e-1:this.isP2shScriptSig()?e-2:this.isPubkeyHashScriptSig()?1:0},e.prototype.getSignatures=function(){ret=[];var t=this.chunks.length;if(this.isMultiSigScriptSig())for(var e=1;t>e;e++)ret.push(this.chunks[e]);else if(this.isP2shScriptSig())for(var e=1;t-1>e;e++)ret.push(this.chunks[e]);else this.isPubkeyHashScriptSig()&&ret.push(this.chunks[0]);return ret},e.prototype.getHashType=function(){for(var t=this.getSignatures(),e=null,r=0;ri;i++){var u=this.chunks[i];if(i>0&&(n+=" "),n+=t.isBuffer(u)?"0x"+f.formatBuffer(u,e?null:0):h.reverseMap[u],r&&i>r){n+=" ...";break}}return n},e.prototype.toString=function(t,e){var r=" + + + + + + + + diff --git a/public/views/add.html b/public/views/add.html new file mode 100644 index 000000000..c6285dc1a --- /dev/null +++ b/public/views/add.html @@ -0,0 +1,32 @@ + +
diff --git a/public/views/backup.html b/public/views/backup.html new file mode 100644 index 000000000..a0d185ddc --- /dev/null +++ b/public/views/backup.html @@ -0,0 +1,62 @@ + +
+
+ +
+ + This wallet have its private key encrypted. Exporting a backup will keep the private key encrypted on the backup. + +
+ + +
+ +
+ + +
+ +
+ + + +
+

Backup options

+ + +
+ + +
+
+

Copy backup in a safe place

+
+ + +
+
+ + Copy this text as it is in a safe place (notepad or email) +
+
+
+ +
+ * You can safely install your backup on other device and + use your wallet from many devices at the same time. +
+ +
+ diff --git a/public/views/copayers.html b/public/views/copayers.html new file mode 100644 index 000000000..c2e337d04 --- /dev/null +++ b/public/views/copayers.html @@ -0,0 +1,57 @@ +
+ +
+
+
+

Share this secret with your copayers

+
+
+ +
+
+ +
+ {{index.walletSecret || ('Loading...'|translate)}} +
+
+
+
+ + + Share secret + +
+

+ Waiting for copayers + + [ {{index.m}} of {{index.n}} ] + +

+
+
+

+ + Waiting... +

+
+
+ +
+
+
+

Wallet incomplete and broken

+

Delete it and create a new one

+
+ +
+
+ +
+ + +
+ diff --git a/public/views/create.html b/public/views/create.html new file mode 100644 index 000000000..d07a3d212 --- /dev/null +++ b/public/views/create.html @@ -0,0 +1,80 @@ +
+
Creating wallet...
+
+
+
+
+
+ +
+ + {{create.error|translate}} + +
+ +
+
+ +
+
+ +
+
+
+
+ +
+
+ +
+
+ + +
+
+ +
+
+ + + +
+
+
+
+
diff --git a/public/views/createProfile.html b/public/views/createProfile.html new file mode 100644 index 000000000..ca58a33d1 --- /dev/null +++ b/public/views/createProfile.html @@ -0,0 +1,22 @@ +
+ +
+ Copay +
+
+
+
+

Warning

+
+

You don’t have an internet connection active. Copay needs connection to create a wallet

+
+
+
+ +
+ Creating wallet... +
+
+
+ diff --git a/views/devLogin.html b/public/views/devLogin.html similarity index 100% rename from views/devLogin.html rename to public/views/devLogin.html diff --git a/views/dummy-translations.html b/public/views/dummy-translations.html similarity index 100% rename from views/dummy-translations.html rename to public/views/dummy-translations.html diff --git a/public/views/history.html b/public/views/history.html new file mode 100644 index 000000000..cf1c54ffc --- /dev/null +++ b/public/views/history.html @@ -0,0 +1,56 @@ +
+
+
+
+ Getting transactions... + No transactions yet +
+
+
+
+
+
+ Received + Sent + Moved +
+ +
+ + + + - + {{history.formatAmount(btx.amount)}} + {{history.getUnitName()}} + +
+
+
+ + + Unconfirmed + +
+
+
+ {{btx.message || btx.addressTo}} +
+
+
+
+ +
+
+
+
+
diff --git a/public/views/import.html b/public/views/import.html new file mode 100644 index 000000000..906d08c40 --- /dev/null +++ b/public/views/import.html @@ -0,0 +1,45 @@ +
+
Importing wallet...
+
+
+
+
+
+ +
+ + {{import.error|translate}} + +
+ +
+ + +
+ +
+ + +
+ + +
+ +
+ +
+
+
+
+ +
diff --git a/public/views/importLegacy.html b/public/views/importLegacy.html new file mode 100644 index 000000000..9faeae7fa --- /dev/null +++ b/public/views/importLegacy.html @@ -0,0 +1,75 @@ +
+
+
+
+ Copay +
+
+
+
+

Importing...

+
    +
  • + {{m.message}} +
+
+ +
+
+ +
+ + {{importLegacy.error|translate}} + +
+ +
+
+ + + + + + + + + + +
+ +
+
+
+ +
+ + {{error|translate}} + +
+
+
+ + +
+
+
+ diff --git a/views/importProfile.html b/public/views/importProfile.html similarity index 77% rename from views/importProfile.html rename to public/views/importProfile.html index 269df3db5..ff6b4ef30 100644 --- a/views/importProfile.html +++ b/public/views/importProfile.html @@ -1,5 +1,5 @@ -
+
@@ -11,8 +11,8 @@
-

Import Profile

-
+

Import a profile

+
@@ -25,23 +25,24 @@
+ placeholder="{{'Select a backup file'|translate}}" name="backupFile" ng-model="importProfile.backupFile" ng-file-select>
-
+
- +
@@ -53,7 +54,7 @@