From 5e7300987ca12d0953f2b58eeaf245e2387ffbe4 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 28 May 2018 13:56:12 +0300 Subject: [PATCH] Updated on 2026-08-14 --- .gitignore | 10 + .idea/caches/build_file_checksums.ser | Bin 0 -> 536 bytes .idea/codeStyles/Project.xml | 29 + .idea/gradle.xml | 18 + .idea/misc.xml | 34 + .idea/runConfigurations.xml | 12 + app/.gitignore | 1 + app/build.gradle | 54 + app/proguard-rules.pro | 21 + .../tangem/wallet/ExampleInstrumentedTest.kt | 24 + app/src/main/AndroidManifest.xml | 136 ++ app/src/main/ic_fp-web.png | Bin 0 -> 56043 bytes app/src/main/ic_launcher-web.png | Bin 0 -> 8020 bytes app/src/main/ic_launcher_round-web.png | Bin 0 -> 25750 bytes app/src/main/ic_logo_small-web.png | Bin 0 -> 14897 bytes app/src/main/java/com/tangem/User.java | 4 + .../com/tangem/cardReader/CardCrypto.java | 243 ++ .../com/tangem/cardReader/CardProtocol.java | 954 ++++++++ .../com/tangem/cardReader/CommandApdu.java | 277 +++ .../tangem/cardReader/CustomCardReader.java | 86 + .../main/java/com/tangem/cardReader/INS.java | 43 + .../tangem/cardReader/NFCEnableDialog.java | 45 + .../com/tangem/cardReader/NfcManager.java | 153 ++ .../java/com/tangem/cardReader/PBKDF2.java | 103 + .../com/tangem/cardReader/ResponseApdu.java | 125 + .../main/java/com/tangem/cardReader/SW.java | 43 + .../com/tangem/cardReader/SettingsMask.java | 25 + .../main/java/com/tangem/cardReader/TLV.java | 222 ++ .../com/tangem/cardReader/TLVException.java | 18 + .../java/com/tangem/cardReader/TLVList.java | 72 + .../main/java/com/tangem/cardReader/Util.java | 959 ++++++++ .../main/java/com/tangem/wallet/BTCUtils.java | 519 ++++ .../main/java/com/tangem/wallet/Base58.java | 115 + .../com/tangem/wallet/BitcoinException.java | 35 + .../com/tangem/wallet/BitcoinInputStream.java | 69 + .../tangem/wallet/BitcoinOutputStream.java | 42 + .../java/com/tangem/wallet/Blockchain.java | 99 + .../java/com/tangem/wallet/BtcCashEngine.java | 428 ++++ .../java/com/tangem/wallet/BtcEngine.java | 564 +++++ .../main/java/com/tangem/wallet/ByteUtil.java | 39 + .../com/tangem/wallet/CardInfoActivity.java | 122 + .../com/tangem/wallet/CardListAdapter.java | 599 +++++ .../tangem/wallet/CardUnspentListAdapter.java | 83 + .../java/com/tangem/wallet/CoinEngine.java | 82 + .../com/tangem/wallet/CoinEngineFactory.java | 23 + .../tangem/wallet/ConfirmPaymentActivity.java | 732 ++++++ .../wallet/CreateNewWalletActivity.java | 330 +++ .../java/com/tangem/wallet/CryptoUtil.java | 270 +++ .../com/tangem/wallet/DerEncodingUtil.java | 130 + .../wallet/DeviceNFCAntennaLocation.java | 25 + .../java/com/tangem/wallet/DeviceName.java | 2091 +++++++++++++++++ .../main/java/com/tangem/wallet/Digest.java | 114 + .../java/com/tangem/wallet/DigestEngine.java | 220 ++ .../com/tangem/wallet/ECDSASignature_ETH.java | 52 + .../com/tangem/wallet/ETH_Transaction.java | 229 ++ .../com/tangem/wallet/Electrum_Request.java | 184 ++ .../java/com/tangem/wallet/Electrum_Task.java | 116 + .../tangem/wallet/EmptyWalletActivity.java | 356 +++ .../java/com/tangem/wallet/EthEngine.java | 392 +++ .../com/tangem/wallet/ExchangeRequest.java | 92 + .../java/com/tangem/wallet/ExchangeTask.java | 66 + .../java/com/tangem/wallet/Fee_Request.java | 102 + .../main/java/com/tangem/wallet/Fee_Task.java | 63 + .../com/tangem/wallet/FingerprintHelper.java | 62 + .../java/com/tangem/wallet/FormatUtil.java | 51 + .../com/tangem/wallet/Infura_Request.java | 197 ++ .../java/com/tangem/wallet/Infura_Task.java | 134 ++ .../main/java/com/tangem/wallet/Issuer.java | 126 + .../java/com/tangem/wallet/Keccak256.java | 39 + .../java/com/tangem/wallet/KeccakCore.java | 546 +++++ .../com/tangem/wallet/LastSignStorage.java | 146 ++ .../tangem/wallet/LoadedWalletActivity.java | 37 + .../wallet/LoadedWalletActivityFragment.java | 1599 +++++++++++++ .../com/tangem/wallet/LogFileProvider.java | 108 + .../main/java/com/tangem/wallet/Logger.java | 285 +++ .../java/com/tangem/wallet/LogoActivity.java | 68 + .../java/com/tangem/wallet/MainActivity.java | 404 ++++ .../tangem/wallet/MainActivityFragment.java | 1038 ++++++++ .../java/com/tangem/wallet/Manufacturer.java | 126 + .../wallet/NoExtendedLengthSupportDialog.java | 34 + .../java/com/tangem/wallet/PINStorage.java | 206 ++ .../java/com/tangem/wallet/PhoneUtility.java | 35 + .../tangem/wallet/PreparePaymentActivity.java | 237 ++ .../java/com/tangem/wallet/PurgeActivity.java | 333 +++ .../com/tangem/wallet/QRScanActivity.java | 90 + app/src/main/java/com/tangem/wallet/RLP.java | 228 ++ .../java/com/tangem/wallet/RLPElement.java | 12 + .../main/java/com/tangem/wallet/RLPList.java | 37 + .../com/tangem/wallet/RequestPINActivity.java | 531 +++++ .../com/tangem/wallet/SavePINActivity.java | 543 +++++ .../wallet/SelectBlockchainActivity.java | 31 + .../wallet/SendTransactionActivity.java | 208 ++ .../java/com/tangem/wallet/SharedData.java | 22 + .../tangem/wallet/SignPaymentActivity.java | 1 + .../com/tangem/wallet/SwapPINActivity.java | 326 +++ .../java/com/tangem/wallet/Tangem_Card.java | 1365 +++++++++++ .../java/com/tangem/wallet/TokenEngine.java | 436 ++++ .../java/com/tangem/wallet/Transaction.java | 632 +++++ .../com/tangem/wallet/UnspentOutputInfo.java | 28 + .../com/tangem/wallet/VerifyCardActivity.java | 28 + .../wallet/VerifyCardActivityFragment.java | 360 +++ .../com/tangem/wallet/VerifyCardTask.java | 109 + .../com/tangem/wallet/VerticalTextView.java | 70 + .../wallet/WaitSecurityDelayDialog.java | 166 ++ .../com/tangem/wallet/WalletInfoFragment.java | 151 ++ .../main/res/drawable-hdpi/ic_show_menu.png | Bin 0 -> 144 bytes .../main/res/drawable-mdpi/ic_show_menu.png | Bin 0 -> 123 bytes .../drawable-v24/ic_launcher_foreground.xml | 34 + .../main/res/drawable-xhdpi/ic_show_menu.png | Bin 0 -> 148 bytes .../main/res/drawable-xxhdpi/ic_show_menu.png | Bin 0 -> 207 bytes app/src/main/res/drawable/bat_token.png | Bin 0 -> 10890 bytes app/src/main/res/drawable/bitcoin_cash.png | Bin 0 -> 17364 bytes app/src/main/res/drawable/bitcoins.png | Bin 0 -> 12374 bytes .../main/res/drawable/bitcoins_testnet.png | Bin 0 -> 20321 bytes app/src/main/res/drawable/border.xml | 5 + app/src/main/res/drawable/card_bg.png | Bin 0 -> 94143 bytes app/src/main/res/drawable/chain_lookup.png | Bin 0 -> 12899 bytes app/src/main/res/drawable/coinmarketcap.png | Bin 0 -> 11905 bytes app/src/main/res/drawable/ert_token.png | Bin 0 -> 22141 bytes app/src/main/res/drawable/ethereum.png | Bin 0 -> 2998 bytes app/src/main/res/drawable/ethereum2.png | Bin 0 -> 10416 bytes .../main/res/drawable/ethereum_testnet.png | Bin 0 -> 13498 bytes app/src/main/res/drawable/hand_card.png | Bin 0 -> 15692 bytes .../main/res/drawable/ic_action_nfc_gray.png | Bin 0 -> 536 bytes app/src/main/res/drawable/ic_camera.xml | 12 + .../res/drawable/ic_developer_version.png | Bin 0 -> 3319 bytes app/src/main/res/drawable/ic_fp_40px.png | Bin 0 -> 4001 bytes app/src/main/res/drawable/ic_info.png | Bin 0 -> 6701 bytes .../res/drawable/ic_launcher_background.xml | 170 ++ .../res/drawable/ic_launcher_foreground.xml | 34 + .../res/drawable/ic_local_atm_black_24dp.xml | 9 + app/src/main/res/drawable/ic_logo_small.png | Bin 0 -> 6970 bytes app/src/main/res/drawable/ic_search_gray.png | Bin 0 -> 734 bytes app/src/main/res/drawable/ic_show_menu.png | Bin 0 -> 3068 bytes app/src/main/res/drawable/icon_oreo.png | Bin 0 -> 6408 bytes app/src/main/res/drawable/lock_pin.png | Bin 0 -> 4251 bytes app/src/main/res/drawable/lock_pin1.png | Bin 0 -> 4717 bytes app/src/main/res/drawable/lock_pin2.png | Bin 0 -> 4915 bytes app/src/main/res/drawable/logo.png | Bin 0 -> 61543 bytes app/src/main/res/drawable/nfc_scan.png | Bin 0 -> 29516 bytes app/src/main/res/drawable/parsed_msg_bg.xml | 13 + .../res/drawable/parsed_msg_bg_pressed.xml | 6 + .../res/drawable/parsed_msg_bg_states.xml | 8 + app/src/main/res/drawable/qr_scan.png | Bin 0 -> 7035 bytes app/src/main/res/drawable/rfid.gif | Bin 0 -> 18373 bytes app/src/main/res/drawable/smartphone.png | Bin 0 -> 4901 bytes app/src/main/res/drawable/tangem.png | Bin 0 -> 18905 bytes app/src/main/res/drawable/tangem2.png | Bin 0 -> 3214 bytes .../res/drawable/tangem_logo_full_new.png | Bin 0 -> 8440 bytes .../res/drawable/tangem_logo_icon_new.png | Bin 0 -> 3214 bytes .../res/drawable/tangem_logo_small_new.png | Bin 0 -> 3109 bytes app/src/main/res/drawable/tap.png | Bin 0 -> 74171 bytes app/src/main/res/drawable/timer.png | Bin 0 -> 5448 bytes app/src/main/res/drawable/unlock_pin.png | Bin 0 -> 4126 bytes app/src/main/res/drawable/unlock_pin1.png | Bin 0 -> 4406 bytes app/src/main/res/drawable/unlock_pin2.png | Bin 0 -> 4547 bytes app/src/main/res/drawable/unnamed.png | Bin 0 -> 13354 bytes app/src/main/res/font/montserrat_light.otf | Bin 0 -> 91496 bytes app/src/main/res/font/raleway_el.ttf | Bin 0 -> 176344 bytes app/src/main/res/font/raleway_r.ttf | Bin 0 -> 178520 bytes .../main/res/layout/activity_card_info.xml | 49 + .../res/layout/activity_confirm_payment.xml | 423 ++++ .../res/layout/activity_create_new_wallet.xml | 56 + .../main/res/layout/activity_empty_wallet.xml | 335 +++ .../res/layout/activity_loaded_wallet.xml | 35 + app/src/main/res/layout/activity_logo.xml | 45 + app/src/main/res/layout/activity_main.xml | 152 ++ .../res/layout/activity_prepare_payment.xml | 264 +++ app/src/main/res/layout/activity_purge.xml | 59 + app/src/main/res/layout/activity_qrscan.xml | 8 + .../main/res/layout/activity_request_pin.xml | 235 ++ app/src/main/res/layout/activity_save_pin.xml | 238 ++ .../res/layout/activity_select_blockchain.xml | 8 + .../res/layout/activity_send_transaction.xml | 47 + .../main/res/layout/activity_sign_payment.xml | 59 + app/src/main/res/layout/activity_swap_pin.xml | 58 + .../main/res/layout/activity_verify_card.xml | 15 + .../main/res/layout/blockchain_list_item.xml | 23 + .../res/layout/card_history_list_item.xml | 23 + app/src/main/res/layout/card_list_item.xml | 358 +++ .../res/layout/card_unspent_list_item.xml | 23 + .../main/res/layout/content_loaded_wallet.xml | 9 + app/src/main/res/layout/content_main.xml | 9 + .../main/res/layout/content_verify_card.xml | 9 + .../dialog_fingerprint_confirmation.xml | 30 + app/src/main/res/layout/dialog_wait_pin2.xml | 75 + .../res/layout/fragment_loaded_wallet.xml | 580 +++++ app/src/main/res/layout/fragment_main.xml | 34 + .../main/res/layout/fragment_verify_card.xml | 576 +++++ .../res/layout/fragment_wallet_history.xml | 24 + .../main/res/layout/fragment_wallet_info.xml | 44 + .../res/layout/fragment_wallet_unspent.xml | 24 + app/src/main/res/layout/message_list_item.xml | 20 + app/src/main/res/menu/menu_card_info.xml | 10 + app/src/main/res/menu/menu_loaded_wallet.xml | 41 + app/src/main/res/menu/menu_main.xml | 10 + .../res/mipmap-anydpi-v26/ic_launcher.xml | 5 + .../mipmap-anydpi-v26/ic_launcher_round.xml | 5 + app/src/main/res/mipmap-hdpi/ic_fp.png | Bin 0 -> 4767 bytes app/src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 697 bytes .../mipmap-hdpi/ic_launcher_background.png | Bin 0 -> 785 bytes .../mipmap-hdpi/ic_launcher_foreground.png | Bin 0 -> 182 bytes .../res/mipmap-hdpi/ic_launcher_round.png | Bin 0 -> 1837 bytes .../main/res/mipmap-hdpi/ic_logo_small.png | Bin 0 -> 4460 bytes app/src/main/res/mipmap-mdpi/ic_fp.png | Bin 0 -> 3013 bytes app/src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 528 bytes .../mipmap-mdpi/ic_launcher_background.png | Bin 0 -> 458 bytes .../mipmap-mdpi/ic_launcher_foreground.png | Bin 0 -> 125 bytes .../res/mipmap-mdpi/ic_launcher_round.png | Bin 0 -> 1180 bytes .../main/res/mipmap-mdpi/ic_logo_small.png | Bin 0 -> 827 bytes app/src/main/res/mipmap-xhdpi/ic_fp.png | Bin 0 -> 6763 bytes app/src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 1006 bytes .../mipmap-xhdpi/ic_launcher_background.png | Bin 0 -> 1161 bytes .../mipmap-xhdpi/ic_launcher_foreground.png | Bin 0 -> 261 bytes .../res/mipmap-xhdpi/ic_launcher_round.png | Bin 0 -> 2657 bytes .../main/res/mipmap-xhdpi/ic_logo_small.png | Bin 0 -> 1614 bytes app/src/main/res/mipmap-xxhdpi/ic_fp.png | Bin 0 -> 10794 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 1509 bytes .../mipmap-xxhdpi/ic_launcher_background.png | Bin 0 -> 2238 bytes .../mipmap-xxhdpi/ic_launcher_foreground.png | Bin 0 -> 487 bytes .../res/mipmap-xxhdpi/ic_launcher_round.png | Bin 0 -> 4266 bytes .../main/res/mipmap-xxhdpi/ic_logo_small.png | Bin 0 -> 2632 bytes app/src/main/res/mipmap-xxxhdpi/ic_fp.png | Bin 0 -> 15687 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 2251 bytes .../mipmap-xxxhdpi/ic_launcher_background.png | Bin 0 -> 3479 bytes .../mipmap-xxxhdpi/ic_launcher_foreground.png | Bin 0 -> 803 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.png | Bin 0 -> 6338 bytes .../main/res/mipmap-xxxhdpi/ic_logo_small.png | Bin 0 -> 3946 bytes app/src/main/res/values-w820dp/dimens.xml | 6 + app/src/main/res/values/attrs.xml | 12 + app/src/main/res/values/colors.xml | 49 + app/src/main/res/values/dimens.xml | 37 + app/src/main/res/values/strings.xml | 39 + app/src/main/res/values/styles.xml | 58 + app/src/main/res/xml/nfc_tech_filter.xml | 7 + .../java/com/tangem/wallet/ExampleUnitTest.kt | 17 + build.gradle | 27 + gradle.properties | 13 + gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 54708 bytes gradle/wrapper/gradle-wrapper.properties | 6 + gradlew | 172 ++ gradlew.bat | 84 + settings.gradle | 1 + 243 files changed, 28116 insertions(+) create mode 100644 .gitignore create mode 100644 .idea/caches/build_file_checksums.ser create mode 100644 .idea/codeStyles/Project.xml create mode 100644 .idea/gradle.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/runConfigurations.xml create mode 100644 app/.gitignore create mode 100644 app/build.gradle create mode 100644 app/proguard-rules.pro create mode 100644 app/src/androidTest/java/com/tangem/wallet/ExampleInstrumentedTest.kt create mode 100644 app/src/main/AndroidManifest.xml create mode 100644 app/src/main/ic_fp-web.png create mode 100644 app/src/main/ic_launcher-web.png create mode 100644 app/src/main/ic_launcher_round-web.png create mode 100644 app/src/main/ic_logo_small-web.png create mode 100644 app/src/main/java/com/tangem/User.java create mode 100644 app/src/main/java/com/tangem/cardReader/CardCrypto.java create mode 100644 app/src/main/java/com/tangem/cardReader/CardProtocol.java create mode 100644 app/src/main/java/com/tangem/cardReader/CommandApdu.java create mode 100644 app/src/main/java/com/tangem/cardReader/CustomCardReader.java create mode 100644 app/src/main/java/com/tangem/cardReader/INS.java create mode 100644 app/src/main/java/com/tangem/cardReader/NFCEnableDialog.java create mode 100644 app/src/main/java/com/tangem/cardReader/NfcManager.java create mode 100644 app/src/main/java/com/tangem/cardReader/PBKDF2.java create mode 100644 app/src/main/java/com/tangem/cardReader/ResponseApdu.java create mode 100644 app/src/main/java/com/tangem/cardReader/SW.java create mode 100644 app/src/main/java/com/tangem/cardReader/SettingsMask.java create mode 100644 app/src/main/java/com/tangem/cardReader/TLV.java create mode 100644 app/src/main/java/com/tangem/cardReader/TLVException.java create mode 100644 app/src/main/java/com/tangem/cardReader/TLVList.java create mode 100644 app/src/main/java/com/tangem/cardReader/Util.java create mode 100644 app/src/main/java/com/tangem/wallet/BTCUtils.java create mode 100644 app/src/main/java/com/tangem/wallet/Base58.java create mode 100644 app/src/main/java/com/tangem/wallet/BitcoinException.java create mode 100644 app/src/main/java/com/tangem/wallet/BitcoinInputStream.java create mode 100644 app/src/main/java/com/tangem/wallet/BitcoinOutputStream.java create mode 100644 app/src/main/java/com/tangem/wallet/Blockchain.java create mode 100644 app/src/main/java/com/tangem/wallet/BtcCashEngine.java create mode 100644 app/src/main/java/com/tangem/wallet/BtcEngine.java create mode 100644 app/src/main/java/com/tangem/wallet/ByteUtil.java create mode 100644 app/src/main/java/com/tangem/wallet/CardInfoActivity.java create mode 100644 app/src/main/java/com/tangem/wallet/CardListAdapter.java create mode 100644 app/src/main/java/com/tangem/wallet/CardUnspentListAdapter.java create mode 100644 app/src/main/java/com/tangem/wallet/CoinEngine.java create mode 100644 app/src/main/java/com/tangem/wallet/CoinEngineFactory.java create mode 100644 app/src/main/java/com/tangem/wallet/ConfirmPaymentActivity.java create mode 100644 app/src/main/java/com/tangem/wallet/CreateNewWalletActivity.java create mode 100644 app/src/main/java/com/tangem/wallet/CryptoUtil.java create mode 100644 app/src/main/java/com/tangem/wallet/DerEncodingUtil.java create mode 100644 app/src/main/java/com/tangem/wallet/DeviceNFCAntennaLocation.java create mode 100644 app/src/main/java/com/tangem/wallet/DeviceName.java create mode 100644 app/src/main/java/com/tangem/wallet/Digest.java create mode 100644 app/src/main/java/com/tangem/wallet/DigestEngine.java create mode 100644 app/src/main/java/com/tangem/wallet/ECDSASignature_ETH.java create mode 100644 app/src/main/java/com/tangem/wallet/ETH_Transaction.java create mode 100644 app/src/main/java/com/tangem/wallet/Electrum_Request.java create mode 100644 app/src/main/java/com/tangem/wallet/Electrum_Task.java create mode 100644 app/src/main/java/com/tangem/wallet/EmptyWalletActivity.java create mode 100644 app/src/main/java/com/tangem/wallet/EthEngine.java create mode 100644 app/src/main/java/com/tangem/wallet/ExchangeRequest.java create mode 100644 app/src/main/java/com/tangem/wallet/ExchangeTask.java create mode 100644 app/src/main/java/com/tangem/wallet/Fee_Request.java create mode 100644 app/src/main/java/com/tangem/wallet/Fee_Task.java create mode 100644 app/src/main/java/com/tangem/wallet/FingerprintHelper.java create mode 100644 app/src/main/java/com/tangem/wallet/FormatUtil.java create mode 100644 app/src/main/java/com/tangem/wallet/Infura_Request.java create mode 100644 app/src/main/java/com/tangem/wallet/Infura_Task.java create mode 100644 app/src/main/java/com/tangem/wallet/Issuer.java create mode 100644 app/src/main/java/com/tangem/wallet/Keccak256.java create mode 100644 app/src/main/java/com/tangem/wallet/KeccakCore.java create mode 100644 app/src/main/java/com/tangem/wallet/LastSignStorage.java create mode 100644 app/src/main/java/com/tangem/wallet/LoadedWalletActivity.java create mode 100644 app/src/main/java/com/tangem/wallet/LoadedWalletActivityFragment.java create mode 100644 app/src/main/java/com/tangem/wallet/LogFileProvider.java create mode 100644 app/src/main/java/com/tangem/wallet/Logger.java create mode 100644 app/src/main/java/com/tangem/wallet/LogoActivity.java create mode 100644 app/src/main/java/com/tangem/wallet/MainActivity.java create mode 100644 app/src/main/java/com/tangem/wallet/MainActivityFragment.java create mode 100644 app/src/main/java/com/tangem/wallet/Manufacturer.java create mode 100644 app/src/main/java/com/tangem/wallet/NoExtendedLengthSupportDialog.java create mode 100644 app/src/main/java/com/tangem/wallet/PINStorage.java create mode 100644 app/src/main/java/com/tangem/wallet/PhoneUtility.java create mode 100644 app/src/main/java/com/tangem/wallet/PreparePaymentActivity.java create mode 100644 app/src/main/java/com/tangem/wallet/PurgeActivity.java create mode 100644 app/src/main/java/com/tangem/wallet/QRScanActivity.java create mode 100644 app/src/main/java/com/tangem/wallet/RLP.java create mode 100644 app/src/main/java/com/tangem/wallet/RLPElement.java create mode 100644 app/src/main/java/com/tangem/wallet/RLPList.java create mode 100644 app/src/main/java/com/tangem/wallet/RequestPINActivity.java create mode 100644 app/src/main/java/com/tangem/wallet/SavePINActivity.java create mode 100644 app/src/main/java/com/tangem/wallet/SelectBlockchainActivity.java create mode 100644 app/src/main/java/com/tangem/wallet/SendTransactionActivity.java create mode 100644 app/src/main/java/com/tangem/wallet/SharedData.java create mode 100644 app/src/main/java/com/tangem/wallet/SignPaymentActivity.java create mode 100644 app/src/main/java/com/tangem/wallet/SwapPINActivity.java create mode 100644 app/src/main/java/com/tangem/wallet/Tangem_Card.java create mode 100644 app/src/main/java/com/tangem/wallet/TokenEngine.java create mode 100644 app/src/main/java/com/tangem/wallet/Transaction.java create mode 100644 app/src/main/java/com/tangem/wallet/UnspentOutputInfo.java create mode 100644 app/src/main/java/com/tangem/wallet/VerifyCardActivity.java create mode 100644 app/src/main/java/com/tangem/wallet/VerifyCardActivityFragment.java create mode 100644 app/src/main/java/com/tangem/wallet/VerifyCardTask.java create mode 100644 app/src/main/java/com/tangem/wallet/VerticalTextView.java create mode 100644 app/src/main/java/com/tangem/wallet/WaitSecurityDelayDialog.java create mode 100644 app/src/main/java/com/tangem/wallet/WalletInfoFragment.java create mode 100644 app/src/main/res/drawable-hdpi/ic_show_menu.png create mode 100644 app/src/main/res/drawable-mdpi/ic_show_menu.png create mode 100644 app/src/main/res/drawable-v24/ic_launcher_foreground.xml create mode 100644 app/src/main/res/drawable-xhdpi/ic_show_menu.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_show_menu.png create mode 100644 app/src/main/res/drawable/bat_token.png create mode 100644 app/src/main/res/drawable/bitcoin_cash.png create mode 100644 app/src/main/res/drawable/bitcoins.png create mode 100644 app/src/main/res/drawable/bitcoins_testnet.png create mode 100644 app/src/main/res/drawable/border.xml create mode 100644 app/src/main/res/drawable/card_bg.png create mode 100644 app/src/main/res/drawable/chain_lookup.png create mode 100644 app/src/main/res/drawable/coinmarketcap.png create mode 100644 app/src/main/res/drawable/ert_token.png create mode 100644 app/src/main/res/drawable/ethereum.png create mode 100644 app/src/main/res/drawable/ethereum2.png create mode 100644 app/src/main/res/drawable/ethereum_testnet.png create mode 100644 app/src/main/res/drawable/hand_card.png create mode 100644 app/src/main/res/drawable/ic_action_nfc_gray.png create mode 100644 app/src/main/res/drawable/ic_camera.xml create mode 100644 app/src/main/res/drawable/ic_developer_version.png create mode 100644 app/src/main/res/drawable/ic_fp_40px.png create mode 100644 app/src/main/res/drawable/ic_info.png create mode 100644 app/src/main/res/drawable/ic_launcher_background.xml create mode 100644 app/src/main/res/drawable/ic_launcher_foreground.xml create mode 100644 app/src/main/res/drawable/ic_local_atm_black_24dp.xml create mode 100644 app/src/main/res/drawable/ic_logo_small.png create mode 100644 app/src/main/res/drawable/ic_search_gray.png create mode 100644 app/src/main/res/drawable/ic_show_menu.png create mode 100644 app/src/main/res/drawable/icon_oreo.png create mode 100644 app/src/main/res/drawable/lock_pin.png create mode 100644 app/src/main/res/drawable/lock_pin1.png create mode 100644 app/src/main/res/drawable/lock_pin2.png create mode 100644 app/src/main/res/drawable/logo.png create mode 100644 app/src/main/res/drawable/nfc_scan.png create mode 100644 app/src/main/res/drawable/parsed_msg_bg.xml create mode 100644 app/src/main/res/drawable/parsed_msg_bg_pressed.xml create mode 100644 app/src/main/res/drawable/parsed_msg_bg_states.xml create mode 100644 app/src/main/res/drawable/qr_scan.png create mode 100644 app/src/main/res/drawable/rfid.gif create mode 100644 app/src/main/res/drawable/smartphone.png create mode 100644 app/src/main/res/drawable/tangem.png create mode 100644 app/src/main/res/drawable/tangem2.png create mode 100644 app/src/main/res/drawable/tangem_logo_full_new.png create mode 100644 app/src/main/res/drawable/tangem_logo_icon_new.png create mode 100644 app/src/main/res/drawable/tangem_logo_small_new.png create mode 100644 app/src/main/res/drawable/tap.png create mode 100644 app/src/main/res/drawable/timer.png create mode 100644 app/src/main/res/drawable/unlock_pin.png create mode 100644 app/src/main/res/drawable/unlock_pin1.png create mode 100644 app/src/main/res/drawable/unlock_pin2.png create mode 100644 app/src/main/res/drawable/unnamed.png create mode 100644 app/src/main/res/font/montserrat_light.otf create mode 100644 app/src/main/res/font/raleway_el.ttf create mode 100644 app/src/main/res/font/raleway_r.ttf create mode 100644 app/src/main/res/layout/activity_card_info.xml create mode 100644 app/src/main/res/layout/activity_confirm_payment.xml create mode 100644 app/src/main/res/layout/activity_create_new_wallet.xml create mode 100644 app/src/main/res/layout/activity_empty_wallet.xml create mode 100644 app/src/main/res/layout/activity_loaded_wallet.xml create mode 100644 app/src/main/res/layout/activity_logo.xml create mode 100644 app/src/main/res/layout/activity_main.xml create mode 100644 app/src/main/res/layout/activity_prepare_payment.xml create mode 100644 app/src/main/res/layout/activity_purge.xml create mode 100644 app/src/main/res/layout/activity_qrscan.xml create mode 100644 app/src/main/res/layout/activity_request_pin.xml create mode 100644 app/src/main/res/layout/activity_save_pin.xml create mode 100644 app/src/main/res/layout/activity_select_blockchain.xml create mode 100644 app/src/main/res/layout/activity_send_transaction.xml create mode 100644 app/src/main/res/layout/activity_sign_payment.xml create mode 100644 app/src/main/res/layout/activity_swap_pin.xml create mode 100644 app/src/main/res/layout/activity_verify_card.xml create mode 100644 app/src/main/res/layout/blockchain_list_item.xml create mode 100644 app/src/main/res/layout/card_history_list_item.xml create mode 100644 app/src/main/res/layout/card_list_item.xml create mode 100644 app/src/main/res/layout/card_unspent_list_item.xml create mode 100644 app/src/main/res/layout/content_loaded_wallet.xml create mode 100644 app/src/main/res/layout/content_main.xml create mode 100644 app/src/main/res/layout/content_verify_card.xml create mode 100644 app/src/main/res/layout/dialog_fingerprint_confirmation.xml create mode 100644 app/src/main/res/layout/dialog_wait_pin2.xml create mode 100644 app/src/main/res/layout/fragment_loaded_wallet.xml create mode 100644 app/src/main/res/layout/fragment_main.xml create mode 100644 app/src/main/res/layout/fragment_verify_card.xml create mode 100644 app/src/main/res/layout/fragment_wallet_history.xml create mode 100644 app/src/main/res/layout/fragment_wallet_info.xml create mode 100644 app/src/main/res/layout/fragment_wallet_unspent.xml create mode 100644 app/src/main/res/layout/message_list_item.xml create mode 100644 app/src/main/res/menu/menu_card_info.xml create mode 100644 app/src/main/res/menu/menu_loaded_wallet.xml create mode 100644 app/src/main/res/menu/menu_main.xml create mode 100644 app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml create mode 100644 app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml create mode 100644 app/src/main/res/mipmap-hdpi/ic_fp.png create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher_background.png create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-hdpi/ic_logo_small.png create mode 100644 app/src/main/res/mipmap-mdpi/ic_fp.png create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher_background.png create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-mdpi/ic_logo_small.png create mode 100644 app/src/main/res/mipmap-xhdpi/ic_fp.png create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher_background.png create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-xhdpi/ic_logo_small.png create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_fp.png create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher_background.png create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_logo_small.png create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_fp.png create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_logo_small.png create mode 100644 app/src/main/res/values-w820dp/dimens.xml create mode 100644 app/src/main/res/values/attrs.xml create mode 100644 app/src/main/res/values/colors.xml create mode 100644 app/src/main/res/values/dimens.xml create mode 100644 app/src/main/res/values/strings.xml create mode 100644 app/src/main/res/values/styles.xml create mode 100644 app/src/main/res/xml/nfc_tech_filter.xml create mode 100644 app/src/test/java/com/tangem/wallet/ExampleUnitTest.kt create mode 100644 build.gradle create mode 100644 gradle.properties create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100755 gradlew create mode 100644 gradlew.bat create mode 100644 settings.gradle diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000..5edb4eeb07 --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +*.iml +.gradle +/local.properties +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +.DS_Store +/build +/captures +.externalNativeBuild diff --git a/.idea/caches/build_file_checksums.ser b/.idea/caches/build_file_checksums.ser new file mode 100644 index 0000000000000000000000000000000000000000..bab0e7298dc57302ab1eddce838b29c25e00195a GIT binary patch literal 536 zcmZ4UmVvdnh`~NNKUXg?FQq6yGexf?KR>5fFEb@IQ7^qHF(oHeub?PDD>b=9F91S2 zm1gFoxMk*~I%lLNXBU^|7Q2L-Ts|(GuF1r}uGBYr_F>vMNC#JY1CYR(Fc`|U8WE7RMTydUb zSZ}G)%w203JEq92d33*oL0vzzIJKx)KeaSHH7`{kYP>!o#4*gcBHMGbpJ5Zr%LdLF zZ+%xh8M%+ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 0000000000..7ac24c777f --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,18 @@ + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000000..dc3456943d --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml new file mode 100644 index 0000000000..7f68460d8b --- /dev/null +++ b/.idea/runConfigurations.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000000..796b96d1c4 --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000000..319acbd1d6 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,54 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-android-extensions' + +android { + compileSdkVersion 27 + defaultConfig { + applicationId "com.tangem.wallet" + minSdkVersion 23 + targetSdkVersion 27 + versionCode 1 + versionName "1.0" +// versionCode versionBuild +// versionName versionPrefix+"."+getVersionExtentionString(versionBuild) + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" +// implementation 'com.android.support:appcompat-v7:27.1.1' +// testImplementation 'junit:junit:4.12' +// androidTestImplementation 'com.android.support.test:runner:1.0.2' +// androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' + + + implementation fileTree(include: ['*.jar'], dir: 'libs') + implementation 'com.android.support:appcompat-v7:27.1.1' + testImplementation 'junit:junit:4.12' + androidTestImplementation 'com.android.support.test:runner:1.0.2' + androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' + implementation 'com.android.support:design:27.1.1' + implementation 'com.android.support:support-compat:27.1.1' + implementation 'com.android.support.constraint:constraint-layout:1.1.0' + implementation 'com.madgag.spongycastle:core:1.56.0.0' + implementation 'com.madgag.spongycastle:prov:1.56.0.0' + implementation 'com.google.zxing:core:3.3.2' + implementation 'org.bitcoinj:bitcoinj-parent:0.14.4' + implementation 'org.bitcoinj:bitcoinj-core:0.14.4' + implementation 'com.android.support:cardview-v7:27.1.1' + implementation 'com.android.support:support-v4:27.1.1' + testImplementation 'junit:junit:4.12' + implementation 'me.dm7.barcodescanner:zxing:1.9.8' + implementation 'com.scottyab:rootbeer-lib:0.0.6' + implementation 'com.skyfishjy.ripplebackground:library:1.0.1' + implementation 'info.hoang8f:android-segmented:1.0.6' +} diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000000..f1b424510d --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/app/src/androidTest/java/com/tangem/wallet/ExampleInstrumentedTest.kt b/app/src/androidTest/java/com/tangem/wallet/ExampleInstrumentedTest.kt new file mode 100644 index 0000000000..9a7a795e39 --- /dev/null +++ b/app/src/androidTest/java/com/tangem/wallet/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package com.tangem.wallet + +import android.support.test.InstrumentationRegistry +import android.support.test.runner.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getTargetContext() + assertEquals("com.tangem.wallet", appContext.packageName) + } +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..6e54fbb6e7 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,136 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/ic_fp-web.png b/app/src/main/ic_fp-web.png new file mode 100644 index 0000000000000000000000000000000000000000..85a16951a629067f8f4990c38790726400d6e10d GIT binary patch literal 56043 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�`!^QYdXby-~yZRjP)G!sAZ(0}*y8`oQvA`Ad? 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 UBS=_sC3t@x??fsEI=n!hfv&#`MEsKt0WYtNDz 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 F{{d(Wg~|W` literal 0 HcmV?d00001 diff --git a/app/src/main/ic_launcher_round-web.png b/app/src/main/ic_launcher_round-web.png new file mode 100644 index 0000000000000000000000000000000000000000..026a409e8609b36933458fa40efc42b7c18cb02c GIT binary patch literal 25750 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%0ATSNKM`}UpL(+xn@4gZ6x*%?rF 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 WUw25|76pxV0i1=MdD(IAxc>v}yC)$4 literal 0 HcmV?d00001 diff --git a/app/src/main/ic_logo_small-web.png b/app/src/main/ic_logo_small-web.png new file mode 100644 index 0000000000000000000000000000000000000000..76a713ee0160319b32b4e3ac8738d567546647df GIT binary patch literal 14897 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 literal 0 HcmV?d00001 diff --git a/app/src/main/java/com/tangem/User.java b/app/src/main/java/com/tangem/User.java new file mode 100644 index 0000000000..12caa848b6 --- /dev/null +++ b/app/src/main/java/com/tangem/User.java @@ -0,0 +1,4 @@ +package com.tangem; + +public class User { +} diff --git a/app/src/main/java/com/tangem/cardReader/CardCrypto.java b/app/src/main/java/com/tangem/cardReader/CardCrypto.java new file mode 100644 index 0000000000..ee2c66ce3e --- /dev/null +++ b/app/src/main/java/com/tangem/cardReader/CardCrypto.java @@ -0,0 +1,243 @@ +package com.tangem.cardReader; + +import android.util.Log; + +import org.spongycastle.asn1.ASN1EncodableVector; +import org.spongycastle.asn1.ASN1Integer; +import org.spongycastle.asn1.DERSequence; +import org.spongycastle.crypto.params.ECDomainParameters; +import org.spongycastle.crypto.params.ECPrivateKeyParameters; +import org.spongycastle.crypto.signers.ECDSASigner; +import org.spongycastle.jce.ECNamedCurveTable; +import org.spongycastle.jce.spec.ECNamedCurveParameterSpec; +import org.spongycastle.jce.spec.ECPrivateKeySpec; +import org.spongycastle.jce.spec.ECPublicKeySpec; +import org.spongycastle.math.ec.ECPoint; + +import java.math.BigInteger; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.KeyFactory; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.Signature; +import java.security.spec.InvalidKeySpecException; +import java.util.Arrays; + +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.PBEKeySpec; +import javax.crypto.spec.SecretKeySpec; + +/** + * Created by dvol on 14.11.2017. + */ + +public class CardCrypto { + public static PublicKey LoadPublicKey(byte[] publicKeyArray) throws Exception { + ECNamedCurveParameterSpec spec = ECNamedCurveTable.getParameterSpec("secp256k1"); + KeyFactory factory = KeyFactory.getInstance("EC", "SC"); + + ECPoint p1 = spec.getCurve().decodePoint(publicKeyArray); + ECPublicKeySpec keySpec = new ECPublicKeySpec(p1, spec); + + return factory.generatePublic(keySpec); + } + + public static boolean VerifySignature(byte[] publicKeyArray, byte[] data, byte[] signature) throws Exception { + Signature signatureInstance = Signature.getInstance("SHA256withECDSA"); + PublicKey publicKey = LoadPublicKey(publicKeyArray); + signatureInstance.initVerify(publicKey); + signatureInstance.update(data); + + ASN1EncodableVector v = new ASN1EncodableVector(); + int size = signature.length / 2; + v.add(/*r*/new ASN1Integer(new BigInteger(1, Arrays.copyOfRange(signature, 0, size)))); + v.add(/*s*/new ASN1Integer(new BigInteger(1, Arrays.copyOfRange(signature, size, size * 2)))); + byte[] sigDer = new DERSequence(v).getEncoded(); + + return signatureInstance.verify(sigDer); + } + + +// public static boolean isCanonical(BigInteger s) { +// +// ECNamedCurveParameterSpec spec = ECNamedCurveTable.getParameterSpec("secp256k1"); +// +// BigInteger HALF_CURVE_ORDER = spec.getN().shiftRight(1); +// return s.compareTo(HALF_CURVE_ORDER) <= 0; +// } +// +// public static BigInteger toCanonicalised(BigInteger s) { +// +// // The order of the curve is the number of valid points that exist on that curve. If S is in the upper +// // half of the number of valid points, then bring it back to the lower half. Otherwise, imagine that +// // N = 10 +// // s = 8, so (-8 % 10 == 2) thus both (r, 8) and (r, 2) are valid solutions. +// // 10 - 8 == 2, giving us always the latter solution, which is canonical. +// ECNamedCurveParameterSpec spec = ECNamedCurveTable.getParameterSpec("secp256k1"); +// if(!isCanonical(s)) { +// BigInteger canon = spec.getN().subtract(s); +// //Log.e("TX_SIGN", "non Canonical S"); +// return canon; +// } +// +// return s; +// +// } +// +// public static BigInteger[] calcSign2(byte[] priv, byte[] hash) +// { +// ECDSASigner signer = new ECDSASigner(); +// BigInteger d = new BigInteger(priv); +// +// ECNamedCurveParameterSpec CURVE_PARAMS = ECNamedCurveTable.getParameterSpec("secp256k1"); +// ECDomainParameters CURVE = new ECDomainParameters(CURVE_PARAMS.getCurve(), CURVE_PARAMS.getG(), CURVE_PARAMS.getN(), +// CURVE_PARAMS.getH()); +// ECPrivateKeyParameters params = new ECPrivateKeyParameters(d, CURVE); +// //ECPublicKeyParameters params = new ECPublicKeyParameters(CURVE.getCurve().decodePoint(pub), CURVE); +// signer.init(true, params); +// BigInteger[] rs = signer.generateSignature(hash); +// return rs; +// +// } +// +// public static byte[] Signature3(byte[] privateKeyArray, byte[] data) throws Exception +// { +// byte[] hash=Util.calculateSHA256(data); +// BigInteger[] signBI=calcSign2(privateKeyArray, hash); +// //signBI[0]=toCanonicalised(signBI[0]); +// signBI[1]=toCanonicalised(signBI[1]); +// byte[] r = signBI[0].toByteArray(); +// byte[] s = signBI[1].toByteArray(); +// +// byte[] res = new byte[64]; +// if( r.length==32 ) { +// System.arraycopy(r, 0, res, 0, r.length); +// }else if( r.length==33 && r[0]==0 ){ +// System.arraycopy(r, 1, res, 0, r.length-1); +// }else { +// throw new Exception("unsupported r-length"); +// } +// if( s.length==32 ) { +// System.arraycopy(s, 0, res, 32, 32); +// }else{ +// throw new Exception("unsupported s-length"); +// } +// return res; +// } + + public static byte[] Signature(byte[] privateKeyArray, byte[] data) throws Exception { + ECNamedCurveParameterSpec spec = ECNamedCurveTable.getParameterSpec("secp256k1"); + KeyFactory factory = KeyFactory.getInstance("EC", "SC"); + + ECPrivateKeySpec keySpecP = new ECPrivateKeySpec(new BigInteger(1,privateKeyArray), spec); + + Signature signature = Signature.getInstance("SHA256withECDSA"); + + PrivateKey privateKey = factory.generatePrivate(keySpecP); + signature.initSign(privateKey); + signature.update(data); + byte[] enc = signature.sign(); + + if (enc[0] != 0x30) throw new Exception("bad encoding 1"); + if ((enc[1] & 0x80) != 0) throw new Exception("unsupported length encoding 1"); + if (enc[2] != 0x02) throw new Exception("bad encoding 2"); + if ((enc[3] & 0x80) != 0) throw new Exception("unsupported length encoding 2"); + int rLength = enc[3]; + + if (enc[4 + rLength] != 0x02) throw new Exception("bad encoding 3"); + if ((enc[5 + rLength] & 0x80) != 0) throw new Exception("unsupported length encoding 3"); + int sLength = enc[5 + rLength]; + + + int sPos = 6 + rLength; + byte[] res = new byte[64]; + if (rLength <= 32) { + System.arraycopy(enc, 4, res, 32-rLength, rLength); + rLength=32; + } else if (rLength == 33 && enc[4] == 0) { + rLength--; + System.arraycopy(enc, 5, res, 0, rLength); + } else { + Log.e("cardCrypto","r-length:" + String.valueOf(rLength)); + Log.e("cardCrypto","s-length:" + String.valueOf(sLength)); + Log.e("cardCrypto","enc:" +Util.bytesToHex(enc)); + throw new Exception("unsupported r-length - r-length:" + String.valueOf(rLength)+",s-length:" + String.valueOf(sLength)+",enc:" +Util.bytesToHex(enc)); + } + if (sLength <= 32) { + System.arraycopy(enc, sPos, res, rLength+32-sLength, sLength); + sLength=32; + } else if (sLength == 33 && enc[sPos] == 0) { + System.arraycopy(enc, sPos + 1, res, rLength, sLength - 1); + } else { + Log.e("cardCrypto","s-length:" + String.valueOf(sLength)); + Log.e("cardCrypto","r-length:" + String.valueOf(rLength)); + Log.e("cardCrypto","enc:" +Util.bytesToHex(enc)); + throw new Exception("unsupported s-length - r-length:" + String.valueOf(rLength)+",s-length:" + String.valueOf(sLength)+",enc:" +Util.bytesToHex(enc)); + } + + if(!VerifySignature(GeneratePublicKey(privateKeyArray), data, res)) + { + throw new Exception("Signature self verify failed - r-length:" + String.valueOf(rLength)+",s-length:" + String.valueOf(sLength)+",enc:" +Util.bytesToHex(enc)+",res:"+Util.bytesToHex(res)); + } + + return res; + } + + public static byte[] GeneratePublicKey(byte[] privateKeyArray) throws NoSuchProviderException, NoSuchAlgorithmException { + ECNamedCurveParameterSpec spec = ECNamedCurveTable.getParameterSpec("secp256k1"); + + + byte[] publicKeyArray = spec.getG().multiply(new BigInteger(1,privateKeyArray)).getEncoded(false); + + return publicKeyArray; + } + + /** + * Computes the PBKDF2 hash of a password. + * + * @param password the password to hash. + * @param salt the salt + * @param iterations the iteration count (slowness factor) + * @return the PBDKF2 hash of the password + */ + public static byte[] pbkdf2(byte[] password, byte[] salt, int iterations) + throws NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException { + return PBKDF2.deriveKey(password, salt, iterations); + } + + public static byte[] Encrypt(byte[] key, byte[] data) throws NoSuchAlgorithmException, NoSuchProviderException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException + { + SecretKeySpec skeySpec = new SecretKeySpec(key, "AES/CBC/PKCS7PADDING"); + Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7PADDING", "BC"); + cipher.init(Cipher.ENCRYPT_MODE, skeySpec, new IvParameterSpec(new byte[16])); + byte[] mEncryptedData = cipher.doFinal(data); + return mEncryptedData; + } + + public static byte[] Decrypt(byte[] key, byte[] data) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException { + try { + SecretKeySpec skeySpec = new SecretKeySpec(key, "AES/CBC/PKCS7PADDING"); + Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7PADDING"); + cipher.init(Cipher.DECRYPT_MODE, skeySpec, new IvParameterSpec(new byte[16])); + byte[] decryptedData = cipher.doFinal(Arrays.copyOfRange(data, 0, data.length )); + return decryptedData; + } + catch (Exception e) + { + SecretKeySpec skeySpec = new SecretKeySpec(key, "AES/CBC/NOPADDING"); + Cipher cipher = Cipher.getInstance("AES/CBC/NOPADDING"); + cipher.init(Cipher.DECRYPT_MODE, skeySpec, new IvParameterSpec(new byte[16])); + byte[] decryptedData = cipher.doFinal(Arrays.copyOfRange(data, 0, data.length )); + Log.e("decrypt",Util.bytesToHex(decryptedData)); + throw e; + } + } +} diff --git a/app/src/main/java/com/tangem/cardReader/CardProtocol.java b/app/src/main/java/com/tangem/cardReader/CardProtocol.java new file mode 100644 index 0000000000..3e0a9f1062 --- /dev/null +++ b/app/src/main/java/com/tangem/cardReader/CardProtocol.java @@ -0,0 +1,954 @@ +package com.tangem.cardReader; + +import android.content.Context; +import android.nfc.Tag; +import android.nfc.TagLostException; +import android.nfc.tech.IsoDep; +import android.util.Log; + +import com.tangem.wallet.CoinEngine; +import com.tangem.wallet.CoinEngineFactory; +import com.tangem.wallet.Issuer; +import com.tangem.wallet.Tangem_Card; +import com.tangem.wallet.Manufacturer; + +import org.spongycastle.jce.ECNamedCurveTable; +import org.spongycastle.jce.interfaces.ECPublicKey; +import org.spongycastle.jce.spec.ECNamedCurveParameterSpec; +import org.spongycastle.math.ec.ECPoint; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.security.InvalidKeyException; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.SecureRandom; +import java.security.Security; +import java.security.spec.ECGenParameterSpec; +import java.security.spec.InvalidKeySpecException; +import java.util.Calendar; + +import javax.crypto.KeyAgreement; + + +/** + * Created by dvol on 14.07.2017. + */ + +public class CardProtocol { + + + public interface Notifications { + void OnReadStart(CardProtocol cardProtocol); + + void OnReadProgress(CardProtocol cardProtocol, int progress); + + void OnReadFinish(CardProtocol cardProtocol); + + void OnReadCancel(); + + void OnReadWait(int msec); + + void OnReadBeforeRequest(int timeout); + + void OnReadAfterRequest(); + } + + + public void setError(Exception error) { + mError = error; + } + + public Tangem_Card getCard() { + return mCard; + } + + public Exception getError() { + return mError; + } + + private static final String logTag = "CardProtocol"; + public static final int SW_PIN_ERROR = SW.INVALID_PARAMS; + + public static final String DefaultPIN = "000000"; + public static final String DefaultPIN2 = "000"; + + protected IsoDep mIsoDep; + + public Tag getTag() { + return mIsoDep.getTag(); + } + + protected String mPIN; + protected Notifications mNotifications; + + public void setPIN(String PIN) { + mPIN = PIN; + if (mCard != null) { + mCard.setPIN(PIN); + } + } + + protected Tangem_Card mCard; + protected Exception mError; + protected Context mContext; + + static { + Security.insertProviderAt(new org.spongycastle.jce.provider.BouncyCastleProvider(), 1); + } + + public CardProtocol(Context context, IsoDep isoDep, Notifications notifications) { + mContext = context; + mIsoDep = isoDep; + mNotifications = notifications; + mPIN = null; + mCard = new Tangem_Card(Util.byteArrayToHexString(mIsoDep.getTag().getId())); + } + + public CardProtocol(Context context, IsoDep isoDep, Tangem_Card card, Notifications notifications) { + mContext = context; + mIsoDep = isoDep; + mNotifications = notifications; + mPIN = card.getPIN(); + mCard = card; + } + + public static class TangemException extends Exception { + public TangemException(String message) { + super(message); + } + } + + public static class TangemException_InvalidPIN extends TangemException { + public TangemException_InvalidPIN(String message) { + super(message); + } + } + + public static class TangemException_NeedPause extends TangemException { + public TangemException_NeedPause(String message) { + super(message); + } + } + + public static class TangemException_ExtendedLengthNotSupported extends TangemException { + public TangemException_ExtendedLengthNotSupported(String message) { + super(message); + } + } + + + public byte[] GetUID() { + if (mIsoDep == null || mIsoDep.getTag() == null) return null; + return mIsoDep.getTag().getId(); + } + + public int getTimeout() { + if (mIsoDep == null || mIsoDep.getTag() == null) return 60000; + return mIsoDep.getTimeout(); + } + + + protected byte[] protocolKey; + + public void resetProtocolKey() { + protocolKey = null; + } + + public void CreateProtocolKey() throws NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException { + protocolKey = CardCrypto.pbkdf2(Util.calculateSHA256(mPIN), mIsoDep.getTag().getId(), 50); + //Log.e("Reader", String.format("PIN: %s, Protocol key: %s", mPIN, Util.bytesToHex(protocolKey))); + if (sessionKey != null) { + sessionKey = null; + } + } + + byte[] sessionKey = null; + + public void run_OpenSession(Tangem_Card.EncryptionMode encryptionMode) throws Exception { + sessionKey = null; + try { + CommandApdu cmdApdu = new CommandApdu(CommandApdu.ISO_CLA, INS.OpenSession.Code, 0, encryptionMode.getP()); + + switch (encryptionMode) { + case Fast: { + byte[] baMyChallenge = Util.generateRandomBytes(16); + + cmdApdu.addTLV(TLV.Tag.TAG_Session_Key_A, baMyChallenge); + Log.i(logTag, cmdApdu.getCommandName()); + ResponseApdu rspApdu = null; + + try { + if (mIsoDep == null) { + throw new TagLostException(); + } + byte[] cmdBytes = cmdApdu.toBytes(); + String cmdStr = CommandApdu.toString(cmdBytes, cmdApdu.getLc()); + Log.v("NFC", String.format("<< [%s]: %s", cmdApdu.getCommandName(), cmdStr)); + + byte[] rsp = mIsoDep.transceive(cmdBytes); + rspApdu = new ResponseApdu(rsp); + + Log.v("NFC", String.format(">> [%s]: %s", cmdApdu.getCommandName(), Util.bytesToHex(rsp))); + + if (rspApdu.isParsedWithError()) { + throw new Exception("Can't parse answer"); + } + } catch (Exception E) { + sessionKey = null; + throw E; + } + + if (rspApdu.isStatus(SW.PROCESS_COMPLETED)) { + Log.i(logTag, String.format("OK: [%04X]\n%s", rspApdu.getSW1SW2(), rspApdu.getTLVs().getParsedTLVs(" "))); + byte[] baTheirsChallenge = rspApdu.getTLVs().getTLV(TLV.Tag.TAG_Session_Key_B).Value; + if (protocolKey == null) CreateProtocolKey(); + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + outputStream.write(baMyChallenge); + outputStream.write(baTheirsChallenge); + outputStream.write(protocolKey); + sessionKey = Util.calculateSHA256(outputStream.toByteArray()); + //Log.i(logTag, String.format("Session key: %s", Util.bytesToHex(sessionKey))); + } else { + Log.e(logTag, String.format("Failed: %04X - %s", rspApdu.getSW1SW2(), rspApdu.getSW1SW2Description())); + throw new Exception(String.format("Can't open session: SW - %04X", rspApdu.getSW1SW2())); + } + } + break; + case Strong: { + KeyPairGenerator kpgen = KeyPairGenerator.getInstance("ECDH", "SC"); + kpgen.initialize(new ECGenParameterSpec("secp256k1"), new SecureRandom()); + KeyPair KP = kpgen.generateKeyPair(); + KeyAgreement ka = KeyAgreement.getInstance("ECDH", "SC"); + ka.init(KP.getPrivate()); + + ECNamedCurveParameterSpec spec = ECNamedCurveTable.getParameterSpec("secp256k1"); + //return spec.getG().multiply(new BigInteger((ECPrivateKey) )).getEncoded(false); + ECPublicKey eckey = (ECPublicKey) KP.getPublic(); + byte[] baMyPublicKey = eckey.getQ().getEncoded(false); + + cmdApdu.addTLV(TLV.Tag.TAG_Session_Key_A, baMyPublicKey); + Log.i(logTag, cmdApdu.getCommandName()); + ResponseApdu rspApdu = null; + + try { + if (mIsoDep == null) { + throw new TagLostException(); + } + //mIsoDep.setTimeout(msTimeout); + + byte[] cmdBytes = cmdApdu.toBytes(); + String cmdStr = CommandApdu.toString(cmdBytes, cmdApdu.getLc()); + Log.v("NFC", String.format("<< [%s]: %s", cmdApdu.getCommandName(), cmdStr)); + byte[] rsp = mIsoDep.transceive(cmdBytes); + rspApdu = new ResponseApdu(rsp); + Log.v("NFC", String.format(">> [%s]: %s", cmdApdu.getCommandName(), Util.bytesToHex(rsp))); + + if (rspApdu.isParsedWithError()) { + throw new Exception("Can't parse answer"); + } + } catch (Exception E) { + sessionKey = null; + throw E; + } + + if (rspApdu.isStatus(SW.PROCESS_COMPLETED)) { + Log.i(logTag, String.format("OK: [%04X]\n%s", rspApdu.getSW1SW2(), rspApdu.getTLVs().getParsedTLVs(" "))); + byte[] baTheirsPublicKey = rspApdu.getTLVs().getTLV(TLV.Tag.TAG_Session_Key_B).Value; + ka.doPhase(CardCrypto.LoadPublicKey(baTheirsPublicKey), true); + if (protocolKey == null) CreateProtocolKey(); + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + outputStream.write(ka.generateSecret()); + outputStream.write(protocolKey); + sessionKey = Util.calculateSHA256(outputStream.toByteArray()); +// Log.i(logTag, String.format("Session key: %s", Util.bytesToHex(sessionKey))); + } else { + Log.i(logTag, String.format("Failed: %04X - %s", rspApdu.getSW1SW2(), rspApdu.getSW1SW2Description())); + throw new Exception(String.format("Can't open session: SW - %04X", rspApdu.getSW1SW2())); + } + } + break; + default: + throw new Exception("Unknown encryption mode"); + } + } catch (Exception e) { + e.printStackTrace(); + Log.e(logTag, String.format("Exception: %s", e.getMessage())); + throw new Exception("Can't open session: " + e.getMessage()); + } + } + + + // send command APDU, get response APDU, and display HEX data to user + private ResponseApdu SendAndReceive(CommandApdu cmdApdu, boolean breakOnNeedPause) throws Exception { + if (mCard.encryptionMode != Tangem_Card.EncryptionMode.None) { + if (sessionKey == null) { + run_OpenSession(mCard.encryptionMode); + } + cmdApdu.Crypt(sessionKey); + } + cmdApdu.setP1(mCard.encryptionMode.getP()); + byte[] cmdBytes = cmdApdu.toBytes(); + String cmdStr = CommandApdu.toString(cmdBytes, cmdApdu.getLc()); + Log.v("NFC", String.format("<< [%s]: %s", cmdApdu.getCommandName(), cmdStr)); + byte[] rsp; + ResponseApdu rspApdu; + try { + do { + try { + mNotifications.OnReadBeforeRequest(mIsoDep.getTimeout()); + try { + rsp = mIsoDep.transceive(cmdBytes); + } finally { + mNotifications.OnReadAfterRequest(); + } + } catch (IOException e) { + if (e.getMessage().contains("length exceeds supported maximum") && mIsoDep.isExtendedLengthApduSupported()) { + throw new TangemException_ExtendedLengthNotSupported(e.getMessage()); + } + throw e; + } + if (mCard.encryptionMode != Tangem_Card.EncryptionMode.None) { + rspApdu = ResponseApdu.Decrypt(rsp, sessionKey); + } else { + rspApdu = new ResponseApdu(rsp); + } + + if (rspApdu.isParsedWithError()) { + Log.v("NFC", String.format(">> [%s]: %s", cmdApdu.getCommandName(), Util.bytesToHex(rsp))); + throw new TangemException(rspApdu.getParseErroMessage()); + } else if (rspApdu.getSW1SW2() == SW.NEED_PAUSE && mNotifications != null) { + int remainingPause = rspApdu.getTLVs().getTagAsInt(TLV.Tag.TAG_Pause) * 10; + Log.v("NFC", String.format(">> Security delay, remaining %f s", remainingPause / 1000.0)); + if (breakOnNeedPause) { + break; + } else { + mNotifications.OnReadWait(remainingPause); + } + } else { + Log.v("NFC", String.format(">> [%s]: %s", cmdApdu.getCommandName(), Util.bytesToHex(rsp))); + } + } while (rspApdu.getSW1SW2() == SW.NEED_PAUSE); + } finally { + mNotifications.OnReadWait(0); + } + + return rspApdu; + } + + private CommandApdu StartPrepareCommand(INS ins) throws NoSuchAlgorithmException { + CommandApdu Apdu = new CommandApdu(ins); + byte[] baPIN = Util.calculateSHA256(mPIN); + Apdu.addTLV(TLV.Tag.TAG_PIN, baPIN); + if (ins != INS.Read) { + Apdu.addTLV(TLV.Tag.TAG_CardID, mCard.getCID()); + } + return Apdu; + } + + public void run_Read() throws Exception { + CommandApdu rqApdu = StartPrepareCommand(INS.Read); + Log.i(logTag, String.format("[%s]\n%s", rqApdu.getCommandName(), rqApdu.getTLVs().getParsedTLVs(" "))); + + ResponseApdu rspApdu = SendAndReceive(rqApdu, false); + + if (rspApdu.isStatus(SW.PROCESS_COMPLETED)) { + + + Log.i(logTag, String.format("OK: [%04X]\n%s", rspApdu.getSW1SW2(), rspApdu.getTLVs().getParsedTLVs(" "))); + + + readResult = rspApdu.getTLVs(); + + parseReadResult(); + + run_ReadOrWriteIssuerDataAndDefineOfflineBalance(); + + } else if (rspApdu.isStatus(SW_PIN_ERROR)) { + throw new TangemException_InvalidPIN(String.format("FAILED: [%04X] - Possible PIN is invalid!\n", rspApdu.getSW1SW2())); + } else { + throw new TangemException(String.format("FAILED: [%04X]\n", rspApdu.getSW1SW2())); + } + } + + public void run_ReadOrWriteIssuerDataAndDefineOfflineBalance() throws Exception { + TLVList tlvIssuerData; + if (mCard.getNeedWriteIssuerData()) { + run_WriteIssuerData(mCard.getIssuerData(), mCard.getIssuerDataSignature()); + try { + tlvIssuerData = TLVList.fromBytes(mCard.getIssuerData()); + } catch (TLVException e) { + e.printStackTrace(); + tlvIssuerData = null; + } + } else { + try { + tlvIssuerData = run_ReadIssuerData(); + } catch (Exception e) { + e.printStackTrace(); + tlvIssuerData = null; + } + } + if (mCard.getStatus() == Tangem_Card.Status.Loaded) { + // try read offline balance data + if (tlvIssuerData != null && tlvIssuerData.getTLV(TLV.Tag.TAG_Denomination) != null && mCard.getMaxSignatures() == mCard.getRemainingSignatures()) { + mCard.setOfflineBalance(tlvIssuerData.getTLV(TLV.Tag.TAG_Denomination).Value); + } else { + mCard.clearOfflineBalance(); + } + } else { + mCard.clearOfflineBalance(); + } + } + + private TLVList readResult = null; + + public void clearReadResult() { + sessionKey = null; + readResult = null; + } + + public boolean haveReadResult() { + return readResult != null; + } + + public void parseReadResult() throws TangemException, NoSuchProviderException, NoSuchAlgorithmException { + TLV tlvStatus = readResult.getTLV(TLV.Tag.TAG_Status); + mCard.setStatus(Tangem_Card.Status.fromCode(Util.byteArrayToInt(tlvStatus.Value))); + TLV tlvCID = readResult.getTLV(TLV.Tag.TAG_CardID); + mCard.setCID(tlvCID.Value); + mCard.setManufacturer(Manufacturer.FindManufacturer(readResult.getTLV(TLV.Tag.TAG_Manufacture_ID).getAsString()), true); + mCard.setHealth(readResult.getTLV(TLV.Tag.TAG_Health).getAsInt()); + + if (mCard.getStatus() != Tangem_Card.Status.NotPersonalized) { + try { + TLV tlvCardPubkicKey = readResult.getTLV(TLV.Tag.TAG_CardPublicKey); + if (tlvCardPubkicKey == null) + throw new TangemException("Invalid answer format"); + mCard.setCardPublicKey(tlvCardPubkicKey.Value); + + TLVList tlvCardData = TLVList.fromBytes(readResult.getTLV(TLV.Tag.TAG_CardData).Value); + + TLV tokenSymbol = tlvCardData.getTLV(TLV.Tag.TAG_Token_Symbol); + TLV contractAddress = tlvCardData.getTLV(TLV.Tag.TAG_Token_Contract_Address); + TLV tokens_decimal = tlvCardData.getTLV(TLV.Tag.TAG_Token_Decimal); + + if (tokenSymbol != null) + mCard.setTokenSymbol(tokenSymbol.getAsString()); + + if (contractAddress != null) + mCard.setContractAddress(contractAddress.getAsString()); + + if (tokens_decimal != null) + mCard.setTokensDecimal(tokens_decimal.getAsInt()); + + // this method has reflection TokenSymbol + // you mast call setBlockchainIDFromCard after calling setTokensDecimal + mCard.setBlockchainIDFromCard(tlvCardData.getTLV(TLV.Tag.TAG_Blockchain_ID).getAsString()); + byte[] tlvPersonalizationDT = tlvCardData.getTLV(TLV.Tag.TAG_ManufactureDateTime).Value; + int year = (tlvPersonalizationDT[0] & 0xFF) << 8 | (tlvPersonalizationDT[1] & 0xFF); + int month = tlvPersonalizationDT[2] - 1; + int day = tlvPersonalizationDT[3]; + Calendar cd = Calendar.getInstance(); + cd.set(year, month, day, 0, 0, 0); + mCard.setPersonalizationDateTime(cd.getTime()); + try { + if (readResult.getTLV(TLV.Tag.TAG_Firmware) != null) { + mCard.setFirmwareVersion(readResult.getTLV(TLV.Tag.TAG_Firmware).getAsString()); + } else { + mCard.setFirmwareVersion(tlvCardData.getTLV(TLV.Tag.TAG_Firmware).getAsString()); + } + } catch (Exception e) { + e.printStackTrace(); + Log.e(logTag, "Cannot get firmware version"); + mCard.setFirmwareVersion("0.00"); + } + + try { + if (mCard.getFirmwareVersion().compareTo("1.05") < 0) { + mCard.setIssuer(Issuer.FindIssuer(tlvCardData.getTLV(TLV.Tag.TAG_Issuer_ID).getAsString(), readResult.getTLV(TLV.Tag.TAG_Issuer_Transaction_PublicKey).Value)); + } else { + mCard.setIssuer(Issuer.FindIssuer(tlvCardData.getTLV(TLV.Tag.TAG_Issuer_ID).getAsString(), readResult.getTLV(TLV.Tag.TAG_Issuer_Data_PublicKey).Value)); + } + } catch (Exception e) { + e.printStackTrace(); + Log.e(logTag, "Cannot get issuer, try a version for older cards"); + try { + mCard.setIssuer(Issuer.FindIssuer(tlvCardData.getTLV(TLV.Tag.TAG_Issuer_ID).getAsString(), readResult.getTLV(TLV.Tag.TAG_Issuer_Transaction_PublicKey).Value)); + } catch (Exception ee) { + ee.printStackTrace(); + Log.e(logTag, "Cannot get issuer"); + mCard.setIssuer(Issuer.Unknown); + } + } + + try { + mCard.setSettingsMask(readResult.getTagAsInt(TLV.Tag.TAG_SettingsMask)); + } catch (Exception e) { + e.printStackTrace(); + Log.e(logTag, "Can't get settings mask"); + } + + if (readResult.getTLV(TLV.Tag.TAG_PauseBeforePIN2) != null) { + mCard.setPauseBeforePIN2(10 * readResult.getTagAsInt(TLV.Tag.TAG_PauseBeforePIN2)); + } + + try { + mCard.setSigningMethod(readResult.getTagAsInt(TLV.Tag.TAG_SigningMethod)); + } catch (Exception e) { + e.printStackTrace(); + Log.e(logTag, "Can't get signing method"); + mCard.setSigningMethod(0); + } + + try { + mCard.setMaxSignatures(readResult.getTagAsInt(TLV.Tag.TAG_MaxSignatures)); + } catch (Exception e) { + e.printStackTrace(); + Log.e(logTag, "Can't get max signatures"); + } + + } catch (Exception e) { + e.printStackTrace(); + throw new TangemException("Can't parse card data"); + } + } + + + if (mCard.getStatus() == Tangem_Card.Status.Loaded) { + + TLV tlvPublicKey = readResult.getTLV(TLV.Tag.TAG_Wallet_PublicKey); + + ECNamedCurveParameterSpec spec = ECNamedCurveTable.getParameterSpec("secp256k1"); + ECPoint p1 = spec.getCurve().decodePoint(tlvPublicKey.Value); + + byte pkUncompressed[] = p1.getEncoded(false); + + byte pkCompresses[] = p1.getEncoded(true); + mCard.setWalletPublicKey(pkUncompressed); + mCard.setWalletPublicKeyRar(pkCompresses); + + CoinEngine engineCoin = CoinEngineFactory.Create(mCard.getBlockchain()); + String wallet = engineCoin.calculateAddress(mCard, pkUncompressed); + mCard.setWallet(wallet); + //mCard.setWallet(Blockchain.calculateWalletAddress(mCard, pkUncompressed)); + + mCard.setRemainingSignatures(readResult.getTagAsInt(TLV.Tag.TAG_RemainingSignatures)); + + } else { + mCard.setWallet("N/A"); + } + } + + + public TLVList run_VerifyCard() throws Exception { + if (mCard.getCardPublicKey() == null || readResult == null) { + run_Read(); + } + if (mCard.getStatus() == Tangem_Card.Status.NotPersonalized) { + getCard().setManufacturer(Manufacturer.Unknown, false); + return null; + } + + if (mCard.getCardPublicKey() == null) { + throw new TangemException("Not all data read, can't verify card!"); + } + + CommandApdu rqApdu = StartPrepareCommand(INS.VerifyCard); + byte[] bChallenge = Util.generateRandomBytes(16); + rqApdu.addTLV(TLV.Tag.TAG_Challenge, bChallenge); + Log.i(logTag, String.format("[%s]\n%s", rqApdu.getCommandName(), rqApdu.getTLVs().getParsedTLVs(" "))); + + ResponseApdu rspApdu = SendAndReceive(rqApdu, false); + + if (rspApdu.isStatus(SW.PROCESS_COMPLETED)) { + TLVList verifyResult = rspApdu.getTLVs(); + Log.i(logTag, String.format("OK: [%04X]\n%s", rspApdu.getSW1SW2(), rspApdu.getTLVs().getParsedTLVs(" "))); + verifyResult.add(new TLV(TLV.Tag.TAG_Challenge, bChallenge)); + + TLV tlvSalt = verifyResult.getTLV(TLV.Tag.TAG_Salt); + TLV tlvCardSignature = verifyResult.getTLV(TLV.Tag.TAG_CardSignature); +// TLV tlvManufacturerSignature = verifyResult.getTLV(TLV.Tag.TAG_Manufacturer_Signature); + + if (tlvSalt == null || tlvCardSignature == null) { + throw new TangemException("Not all data read, can't verify card!"); + } + + try { + ByteArrayOutputStream bs = new ByteArrayOutputStream(); + bs.write(bChallenge); + bs.write(tlvSalt.Value); + byte[] dataArray = bs.toByteArray(); + if (CardCrypto.VerifySignature(mCard.getCardPublicKey(), dataArray, tlvCardSignature.Value)) { + getCard().setCardPublicKeyValid(true); + Log.i(logTag, "Card signature verification OK"); + } else { + Log.e(logTag, "Card signature verification FAILED"); + getCard().setCardPublicKeyValid(false); + } + + //getCard().setManufacturer(Manufacturer.FindManufacturer(readResult.getTLV(TLV.Tag.TAG_Manufacture_ID).getAsString(), bChallenge, tlvSalt.Value, tlvManufacturerSignature.Value), true); + getCard().setManufacturer(Manufacturer.FindManufacturer(readResult.getTLV(TLV.Tag.TAG_Manufacture_ID).getAsString()), true); + } catch (Exception e) { + e.printStackTrace(); + getCard().setManufacturer(Manufacturer.Unknown, false); + } + return verifyResult; + } else if (rspApdu.isStatus(SW_PIN_ERROR)) { + throw new TangemException_InvalidPIN(String.format("FAILED: [%04X] - Invalid PIN\n", rspApdu.getSW1SW2())); + } else { + getCard().setManufacturer(Manufacturer.Unknown, false); + throw new TangemException(String.format("Failed: %04X", rspApdu.getSW1SW2())); + } + + } + + public void run_CreateWallet(String PIN2) throws Exception { + if (readResult == null) run_Read(); + CommandApdu rqApdu = StartPrepareCommand(INS.CreateWallet); + rqApdu.addTLV(TLV.Tag.TAG_PIN2, Util.calculateSHA256(PIN2)); + Log.i(logTag, String.format("[%s]\n%s", rqApdu.getCommandName(), rqApdu.getTLVs().getParsedTLVs(" "))); + + ResponseApdu rspApdu = SendAndReceive(rqApdu, false); + + if (rspApdu.isStatus(SW.PROCESS_COMPLETED)) { + Log.i(logTag, String.format("OK: [%04X]\n%s", rspApdu.getSW1SW2(), rspApdu.getTLVs().getParsedTLVs(" "))); + if (PIN2.equals(DefaultPIN2)) { + mCard.setUseDefaultPIN2(true); + } + } else if (rspApdu.isStatus(SW_PIN_ERROR)) { + if (PIN2.equals(DefaultPIN2)) { + mCard.setUseDefaultPIN2(false); + } + throw new TangemException_InvalidPIN(String.format("FAILED: [%04X] - Invalid PIN\n", rspApdu.getSW1SW2())); + } else { + throw new TangemException(String.format("Failed: %04X", rspApdu.getSW1SW2())); + } + } + + public TLVList run_CheckWallet() throws Exception { + CommandApdu rqApdu = StartPrepareCommand(INS.CheckWallet); + byte[] bChallenge = Util.generateRandomBytes(16); + rqApdu.addTLV(TLV.Tag.TAG_Challenge, bChallenge); + Log.i(logTag, String.format("[%s]\n%s", rqApdu.getCommandName(), rqApdu.getTLVs().getParsedTLVs(" "))); + + ResponseApdu rspApdu = SendAndReceive(rqApdu, false); + + if (rspApdu.isStatus(SW.PROCESS_COMPLETED)) { + TLVList Result = rspApdu.getTLVs(); + Result.add(new TLV(TLV.Tag.TAG_Challenge, bChallenge)); + Log.i(logTag, String.format("OK: [%04X]\n%s", rspApdu.getSW1SW2(), rspApdu.getTLVs().getParsedTLVs(" "))); + return Result; + } else if (rspApdu.isStatus(SW_PIN_ERROR)) { + throw new TangemException_InvalidPIN(String.format("FAILED: [%04X] - Invalid PIN\n", rspApdu.getSW1SW2())); + } else { + throw new TangemException(String.format("Failed: %04X", rspApdu.getSW1SW2())); + } + } + + public void run_CheckWalletWithSignatureVerify() throws Exception { + if (mCard.getCardPublicKey() == null || readResult == null) { + run_Read(); + } + if (mCard.getStatus() == Tangem_Card.Status.NotPersonalized) { + getCard().setManufacturer(Manufacturer.Unknown, false); + return; + } + + if (readResult.getTagAsInt(TLV.Tag.TAG_Status) != Tangem_Card.Status.Loaded.getCode()) { + throw new TangemException("Card must be loaded"); + } + TLVList checkResult = run_CheckWallet(); + if (checkResult == null) return; + + TLV tlvPublicKey = readResult.getTLV(TLV.Tag.TAG_Wallet_PublicKey); + TLV tlvChallenge = checkResult.getTLV(TLV.Tag.TAG_Challenge); + TLV tlvSalt = checkResult.getTLV(TLV.Tag.TAG_Salt); + TLV tlvSignature = checkResult.getTLV(TLV.Tag.TAG_Signature); + + if (tlvPublicKey == null || tlvChallenge == null || tlvSalt == null || tlvSignature == null) { + throw new TangemException("Not all data read, can't check signature!"); + } + + ByteArrayOutputStream bs = new ByteArrayOutputStream(); + bs.write(tlvChallenge.Value); + bs.write(tlvSalt.Value); + byte[] dataArray = bs.toByteArray(); + + if (CardCrypto.VerifySignature(tlvPublicKey.Value, dataArray, tlvSignature.Value)) { + Log.i(logTag, "Signature verification OK"); + mCard.setWalletPublicKeyValid(true); + } else { + mCard.setWalletPublicKeyValid(false); + } + } + + public void run_PurgeWallet(String PIN2) throws Exception { + CommandApdu rqApdu = StartPrepareCommand(INS.PurgeWallet); + rqApdu.addTLV(TLV.Tag.TAG_PIN2, Util.calculateSHA256(PIN2)); + + Log.i(logTag, String.format("[%s]\n%s", rqApdu.getCommandName(), rqApdu.getTLVs().getParsedTLVs(" "))); + + ResponseApdu rspApdu = SendAndReceive(rqApdu, false); + + if (rspApdu.isStatus(SW.PROCESS_COMPLETED)) { + Log.i(logTag, String.format("OK: [%04X]\n%s", rspApdu.getSW1SW2(), rspApdu.getTLVs().getParsedTLVs(" "))); + if (PIN2.equals(DefaultPIN2)) { + mCard.setUseDefaultPIN2(true); + } + } else if (rspApdu.isStatus(SW_PIN_ERROR)) { + if (PIN2.equals(DefaultPIN2)) { + mCard.setUseDefaultPIN2(false); + } + throw new TangemException_InvalidPIN(String.format("FAILED: [%04X] - Invalid PIN\n", rspApdu.getSW1SW2())); + } else { + + throw new TangemException(String.format("Failed: %04X", rspApdu.getSW1SW2())); + } + } + + public void run_SwapPIN(String PIN2, String newPin, String newPin2, boolean breakOnNeedPause) throws Exception { + CommandApdu rqApdu = StartPrepareCommand(INS.SwapPIN); + rqApdu.addTLV(TLV.Tag.TAG_PIN2, Util.calculateSHA256(PIN2)); + rqApdu.addTLV(TLV.Tag.TAG_NewPIN, Util.calculateSHA256(newPin)); + rqApdu.addTLV(TLV.Tag.TAG_NewPIN2, Util.calculateSHA256(newPin2)); + + Log.i(logTag, String.format("[%s]\n%s", rqApdu.getCommandName(), rqApdu.getTLVs().getParsedTLVs(" "))); + + ResponseApdu rspApdu = SendAndReceive(rqApdu, breakOnNeedPause); + + if (rspApdu.isStatus(SW.PIN1_CHANGED) || rspApdu.isStatus(SW.PIN2_CHANGED) || rspApdu.isStatus(SW.PINS_CHANGED) || rspApdu.isStatus(SW.PINS_NOT_CHANGED)) { + Log.i(logTag, String.format("OK: [%04X]\n%s", rspApdu.getSW1SW2(), rspApdu.getTLVs().getParsedTLVs(" "))); + if (newPin2.equals(DefaultPIN2)) { + mCard.setUseDefaultPIN2(true); + } else { + mCard.setUseDefaultPIN2(false); + } + } else if (rspApdu.isStatus(SW_PIN_ERROR)) { + if (PIN2.equals(DefaultPIN2)) { + mCard.setUseDefaultPIN2(false); + } + throw new TangemException_InvalidPIN(String.format("FAILED: [%04X] - Invalid PIN\n", rspApdu.getSW1SW2())); + } else if (breakOnNeedPause && rspApdu.isStatus(SW.NEED_PAUSE)) { + throw new TangemException_NeedPause(String.format("FAILED: [%04X] - Need pause\n", rspApdu.getSW1SW2())); + } else { + throw new TangemException(String.format("Failed: %04X", rspApdu.getSW1SW2())); + } + } + + public void run_CheckPIN2isDefault() throws Exception { + if (mCard.isFirmwareNewer("1.19") || (mCard.isFirmwareNewer("1.12") && (mCard.getPauseBeforePIN2() == 0 || mCard.useSmartSecurityDelay()))) { + // can obtain SwapPIN(to default) answer without security delay - try check if PIN2 is default with card request + try { + run_SwapPIN(DefaultPIN2, mPIN, DefaultPIN2, true); + mCard.setUseDefaultPIN2(true); + } catch (TangemException_NeedPause e) { + mCard.setUseDefaultPIN2(null); + } catch (TangemException_InvalidPIN e) { + mCard.setUseDefaultPIN2(false); + } + } else { + mCard.setUseDefaultPIN2(null); + } + } + + public TLVList run_SignHashes(String PIN2, byte[][] hashes, boolean UseIssuerValidation, byte[] issuerData, Issuer issuer) throws Exception { + ByteArrayOutputStream bs = new ByteArrayOutputStream(); + if (hashes.length > 10) throw new Exception("To much hashes in one transaction!"); + for (int i = 0; i < hashes.length; i++) { + if (i != 0 && hashes[0].length != hashes[i].length) + throw new Exception("Hashes length must be identical!"); + bs.write(hashes[i]); + } + CommandApdu rqApdu = StartPrepareCommand(INS.Sign); + rqApdu.addTLV(TLV.Tag.TAG_PIN2, Util.calculateSHA256(PIN2)); + rqApdu.addTLV_U8(TLV.Tag.TAG_TrOut_HashSize, hashes[0].length); + rqApdu.addTLV(TLV.Tag.TAG_TrOut_Hash, bs.toByteArray()); + if (UseIssuerValidation) { + byte[] issuerSignature = CardCrypto.Signature(issuer.getPrivateTransactionKey(), bs.toByteArray()); + rqApdu.addTLV(TLV.Tag.TAG_Issuer_Transaction_Signature, issuerSignature); + } + if (issuerData != null) { + if (issuer == null || issuer == Issuer.Unknown) + throw new Exception("Need known Issuer to write issuer Data"); + rqApdu.addTLV(TLV.Tag.TAG_Issuer_Data, issuerData); + byte[] issuerSignature = CardCrypto.Signature(issuer.getPrivateTransactionKey(), issuerData); + rqApdu.addTLV(TLV.Tag.TAG_Issuer_Data_Signature, issuerSignature); + } + + + Log.i(logTag, String.format("[%s]\n%s", rqApdu.getCommandName(), rqApdu.getTLVs().getParsedTLVs(" "))); + + ResponseApdu rspApdu = SendAndReceive(rqApdu, false); + + if (rspApdu.isStatus(SW.PROCESS_COMPLETED)) { + TLVList Result = rspApdu.getTLVs(); + Log.i(logTag, String.format("OK: [%04X]\n%s", rspApdu.getSW1SW2(), rspApdu.getTLVs().getParsedTLVs(" "))); + if (PIN2.equals(DefaultPIN2)) { + mCard.setUseDefaultPIN2(true); + } + return Result; + } else if (rspApdu.isStatus(SW_PIN_ERROR)) { + if (PIN2.equals(DefaultPIN2)) { + mCard.setUseDefaultPIN2(false); + } + throw new TangemException_InvalidPIN(String.format("FAILED: [%04X] - Possible the PIN or PIN2 is invalid!\n", rspApdu.getSW1SW2())); + } else { + throw new TangemException(String.format("Failed: %04X", rspApdu.getSW1SW2())); + } + } + + public TLVList run_SignRaw(String PIN2, byte[] bTxOutData) throws Exception { + + CommandApdu rqApdu = StartPrepareCommand(INS.Sign); + rqApdu.addTLV(TLV.Tag.TAG_PIN2, Util.calculateSHA256(PIN2)); + rqApdu.addTLV(TLV.Tag.TAG_TrOut_Raw, bTxOutData); + rqApdu.addTLV(TLV.Tag.TAG_HashAlgID, "sha-256x2".getBytes("US-ASCII")); + Log.i(logTag, String.format("[%s]\n%s", rqApdu.getCommandName(), rqApdu.getTLVs().getParsedTLVs(" "))); + + ResponseApdu rspApdu = SendAndReceive(rqApdu, false); + + if (rspApdu.isStatus(SW.PROCESS_COMPLETED)) { + TLVList Result = rspApdu.getTLVs(); + Result.add(new TLV(TLV.Tag.TAG_TrOut_Raw, bTxOutData)); + Log.i(logTag, String.format("OK: [%04X]\n%s", rspApdu.getSW1SW2(), rspApdu.getTLVs().getParsedTLVs(" "))); + if (PIN2.equals(DefaultPIN2)) { + mCard.setUseDefaultPIN2(true); + } + return Result; + } else if (rspApdu.isStatus(SW_PIN_ERROR)) { + if (PIN2.equals(DefaultPIN2)) { + mCard.setUseDefaultPIN2(false); + } + throw new TangemException_InvalidPIN(String.format("FAILED: [%04X] - Possible the PIN or PIN2 is invalid!\n", rspApdu.getSW1SW2())); + } else { + throw new TangemException(String.format("Failed: %04X", rspApdu.getSW1SW2())); + } + } + + private byte[] run_VerifyCode(String hashAlgID, int codePageAddress, int codePageCount, byte[] challenge) throws Exception { + if (readResult == null) run_Read(); + CommandApdu rqApdu = StartPrepareCommand(INS.VerifyCode); + rqApdu.addTLV(TLV.Tag.TAG_HashAlgID, hashAlgID.getBytes("US-ASCII")); + rqApdu.addTLV_U32(TLV.Tag.TAG_CodePageAddress, codePageAddress); + rqApdu.addTLV_U16(TLV.Tag.TAG_CodePageCount, codePageCount); + rqApdu.addTLV(TLV.Tag.TAG_Challenge, challenge); + + Log.i(logTag, String.format("[%s]\n%s", rqApdu.getCommandName(), rqApdu.getTLVs().getParsedTLVs(" "))); + + ResponseApdu rspApdu = SendAndReceive(rqApdu, false); + + if (rspApdu.isStatus(SW.PROCESS_COMPLETED)) { + Log.i(logTag, String.format("OK: [%04X]\n%s", rspApdu.getSW1SW2(), rspApdu.getTLVs().getParsedTLVs(" "))); + return rspApdu.getTLVs().getTLV(TLV.Tag.TAG_CodeHash).Value; + } else { + throw new TangemException(String.format("Failed: %04X", rspApdu.getSW1SW2())); + } + } + + private void run_ValidateCard(String PIN2) throws Exception { + if (readResult == null) run_Read(); + CommandApdu rqApdu = StartPrepareCommand(INS.ValidateCard); + rqApdu.addTLV(TLV.Tag.TAG_PIN2, Util.calculateSHA256(PIN2)); + + Log.i(logTag, String.format("[%s]\n%s", rqApdu.getCommandName(), rqApdu.getTLVs().getParsedTLVs(" "))); + + ResponseApdu rspApdu = SendAndReceive(rqApdu, false); + + if (rspApdu.isStatus(SW.PROCESS_COMPLETED)) { + Log.i(logTag, String.format("OK: [%04X]\n%s", rspApdu.getSW1SW2(), rspApdu.getTLVs().getParsedTLVs(" "))); + if (PIN2.equals(DefaultPIN2)) { + mCard.setUseDefaultPIN2(true); + } + } else if (rspApdu.isStatus(SW_PIN_ERROR)) { + if (PIN2.equals(DefaultPIN2)) { + mCard.setUseDefaultPIN2(false); + } + throw new TangemException_InvalidPIN(String.format("FAILED: [%04X] - Possible the PIN or PIN2 is invalid!\n", rspApdu.getSW1SW2())); + } else { + throw new TangemException(String.format("Failed: %04X", rspApdu.getSW1SW2())); + } + } + + private void run_WriteIssuerData(byte[] issuerData, byte[] issuerSignature) throws Exception { + run_Read(); + + CommandApdu rqApdu = StartPrepareCommand(INS.WriteIssuerData); + rqApdu.addTLV(TLV.Tag.TAG_Issuer_Data, issuerData); + rqApdu.addTLV(TLV.Tag.TAG_Issuer_Data_Signature, issuerSignature); + + Log.i(logTag, String.format("[%s]\n%s", rqApdu.getCommandName(), rqApdu.getTLVs().getParsedTLVs(" "))); + + ResponseApdu rspApdu = SendAndReceive(rqApdu, false); + + if (rspApdu.isStatus(SW.PROCESS_COMPLETED)) { + Log.i(logTag, String.format("OK: [%04X]\n%s", rspApdu.getSW1SW2(), rspApdu.getTLVs().getParsedTLVs(" "))); + } else { + throw new TangemException(String.format("Failed: %04X", rspApdu.getSW1SW2())); + } + } + + private TLVList run_ReadIssuerData() throws Exception { + CommandApdu rqApdu = StartPrepareCommand(INS.GetIssuerData); + + Log.i(logTag, String.format("[%s]\n%s", rqApdu.getCommandName(), rqApdu.getTLVs().getParsedTLVs(" "))); + + ResponseApdu rspApdu = SendAndReceive(rqApdu, false); + + if (rspApdu.isStatus(SW.PROCESS_COMPLETED)) { + Log.i(logTag, String.format("OK: [%04X]\n%s", rspApdu.getSW1SW2(), rspApdu.getTLVs().getParsedTLVs(" "))); + TLV issuerData = rspApdu.getTLVs().getTLV(TLV.Tag.TAG_Issuer_Data); + TLV issuerDataSignature = rspApdu.getTLVs().getTLV(TLV.Tag.TAG_Issuer_Data_Signature); + + if (issuerData == null || issuerDataSignature == null) + throw new TangemException("Invalid answer format (GetIssuerData)"); + + ByteArrayOutputStream bsDataToVerify = new ByteArrayOutputStream(); + bsDataToVerify.write(mCard.getCID()); + bsDataToVerify.write(issuerData.Value); + try { + if (CardCrypto.VerifySignature(mCard.getIssuer().getPublicDataKey(), bsDataToVerify.toByteArray(), issuerDataSignature.Value)) { + mCard.setIssuerData(issuerData.Value, issuerDataSignature.Value); + return TLVList.fromBytes(issuerData.Value); + } else { + throw new TangemException("Invalid issuer data read (signature verification failed)"); + } + } catch (Exception e) { + e.printStackTrace(); + throw new TangemException("Invalid issuer data read"); + } + } else { + throw new TangemException(String.format("Failed: %04X", rspApdu.getSW1SW2())); + } + } + + public void run_GetSupportedEncryption() throws Exception { + mCard.encryptionMode = Tangem_Card.EncryptionMode.None; + do { + CommandApdu rqApdu = StartPrepareCommand(INS.Read); + Log.i(logTag, String.format("[%s]\n%s", rqApdu.getCommandName(), rqApdu.getTLVs().getParsedTLVs(" "))); + + ResponseApdu rspApdu = SendAndReceive(rqApdu, false); + + if (rspApdu.isStatus(SW.NEED_ENCRYPTION)) { + if (mCard.encryptionMode == Tangem_Card.EncryptionMode.None) { + mCard.encryptionMode = Tangem_Card.EncryptionMode.Fast; + } else if (mCard.encryptionMode == Tangem_Card.EncryptionMode.Fast) { + mCard.encryptionMode = Tangem_Card.EncryptionMode.Strong; + } else { + throw new Exception("Can't get supported encryption methods"); + } + } else if (rspApdu.isStatus(SW.PROCESS_COMPLETED)) { + Log.i(logTag, String.format("OK: [%04X]\n%s", rspApdu.getSW1SW2(), rspApdu.getTLVs().getParsedTLVs(" "))); + readResult = rspApdu.getTLVs(); + mCard.setPIN(mPIN); + break; + } else { + break; + } + } while (true); + } + +} diff --git a/app/src/main/java/com/tangem/cardReader/CommandApdu.java b/app/src/main/java/com/tangem/cardReader/CommandApdu.java new file mode 100644 index 0000000000..3d69198317 --- /dev/null +++ b/app/src/main/java/com/tangem/cardReader/CommandApdu.java @@ -0,0 +1,277 @@ +package com.tangem.cardReader; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; + +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; + +public class CommandApdu { + + public static final byte ISO_CLA = (byte) 0x00; + + protected String mCmdName; + protected int mCla = 0x00; + protected int mIns = 0x00; + protected int mP1 = 0x00; + protected int mP2 = 0x00; + protected int mLc = 0x00; + + protected byte[] mData = new byte[0]; + + protected int mLe = 0x00; + protected boolean mLeUsed = false; + protected TLVList tlvList = new TLVList(); + + public CommandApdu() { + } + + public CommandApdu(int cla, int ins, int p1, int p2) { + setCommandName(ins); + mCla = cla; + mIns = ins; + mP1 = p1; + mP2 = p2; + } + + public CommandApdu(int cla, int ins, int p1, int p2, byte[] data) { + setCommandName(ins); + mCla = cla; + mIns = ins; + mLc = data.length; + mP1 = p1; + mP2 = p2; + mData = data; + } + + public CommandApdu(INS ins) { + setCommandName(ins.name()); + mCla = ISO_CLA; + mIns = ins.Code; + mP1 = 0; + mP2 = 0; + } + + public CommandApdu(int cla, int ins, int p1, int p2, byte[] data, int le) { + setCommandName(ins); + mCla = cla; + mIns = ins; + mLc = data.length; + mP1 = p1; + mP2 = p2; + mData = data; + mLe = le; + mLeUsed = true; + } + + public CommandApdu(int cla, int ins, int p1, int p2, int le) { + setCommandName(ins); + mCla = cla; + mIns = ins; + mP1 = p1; + mP2 = p2; + mLe = le; + mLeUsed = true; + } + + public void setCommandName(String cmdName) { + mCmdName = cmdName; + } + + private void setCommandName(int ins) { + INS ins1 = INS.ByCode(ins); + if (ins1 != null) { + mCmdName = ins1.toString(); + } else { + mCmdName = String.format("INS[%2X]", ins); + } + } + + public String getCommandName() { + return mCmdName; + } + + public void setP1(int p1) { + mP1 = p1; + } + + public void setP2(int p2) { + mP2 = p2; + } + + public void setData(byte[] data) { + mLc = data.length; + mData = data; + } + + public void addTLV(TLV.Tag tag, byte[] value) { + tlvList.add(new TLV(tag, value)); + } + + public void addTLV_U8(TLV.Tag tag, int U8) { + addTLV(tag, new byte[]{(byte) U8}); + } + + public void addTLV_U16(TLV.Tag tag, int U16) { + addTLV(tag, Util.intToByteArray2(U16)); + } + + public void addTLV_U32(TLV.Tag tag, int U32) { + addTLV(tag, Util.intToByteArray4(U32)); + } + + public void setLe(int le) { + mLe = le; + mLeUsed = true; + } + + public int getP1() { + return mP1; + } + + public int getP2() { + return mP2; + } + + public int getLc() { + return mLc; + } + + public byte[] getData() { + return mData; + } + + public TLVList getTLVs() { + return tlvList; + } + + public int getLe() { + return mLe; + } + + public static String toString(byte[] cmdApdu, int Lc) { + String cmd = Util.bytesToHex(cmdApdu); + if (Lc == 0) return cmd; + return cmd.substring(0, 8) + " " + cmd.substring(8, 10) + " " + + cmd.substring(10, 10 + Lc * 2) + " " + cmd.substring(10 + Lc * 2, cmd.length()); + } + + + public void Crypt(byte[] key) throws IllegalBlockSizeException, InvalidKeyException, BadPaddingException, NoSuchAlgorithmException, NoSuchPaddingException, IOException, InvalidAlgorithmParameterException, NoSuchProviderException { + if (tlvList.size() != 0) { + ByteArrayOutputStream stream = new ByteArrayOutputStream(); + for (TLV tlv : tlvList) { + try { + tlv.WriteToStream(stream); + } catch (IOException e) { + e.printStackTrace(); + break; + } + } + + mData = stream.toByteArray(); + byte[] crc = Util.calculateCRC16(mData); + stream = new ByteArrayOutputStream(); + stream.write(Util.intToByteArray2(mData.length)); + stream.write(crc); + stream.write(mData); + mData = stream.toByteArray(); + + byte[] mEncryptedData = CardCrypto.Encrypt(key, mData); + + mData = mEncryptedData; + mLc = mData.length; + + tlvList.clear(); + + } + + } + + public byte[] toBytes() { + int length = 4; // CLA, INS, P1, P2 + + if (tlvList.size() != 0) { + mData = tlvList.toBytes(); + mLc = mData.length; + } + + if (mData.length != 0) { + length += 1; // LC + if (mLc >= 256) + length += 2; + length += mData.length; // DATA + } + if (mLeUsed) { + length += 1; // LE + if (mLc >= 256) + length += 2; + } + + byte[] apdu = new byte[length]; + + int index = 0; + apdu[index] = (byte) mCla; + index++; + apdu[index] = (byte) mIns; + index++; + apdu[index] = (byte) mP1; + index++; + apdu[index] = (byte) mP2; + index++; + if (mLc != 0) { + if (mLc < 256) { + apdu[index] = (byte) mLc; + index++; + } else { + apdu[index] = 0; + index++; + apdu[index] = (byte) (mLc >> 8); + index++; + apdu[index] = (byte) (mLc & 0xFF); + index++; + } + + System.arraycopy(mData, 0, apdu, index, mData.length); + index += mData.length; + } + if (mLeUsed) { + if (mLc < 256) { + apdu[index] += (byte) mLe; // LE + } else { + apdu[index] = 0; + index++; + apdu[index] = (byte) (mLe >> 8); + index++; + apdu[index] = (byte) (mLe & 0xFF); + index++; + } + } + + return apdu; + } + + public CommandApdu clone() { + CommandApdu apdu = new CommandApdu(); + apdu.setCommandName(mCmdName); + apdu.mCla = mCla; + apdu.mIns = mIns; + apdu.mP1 = mP1; + apdu.mP2 = mP2; + apdu.mLc = mLc; + apdu.mData = new byte[mData.length]; + System.arraycopy(mData, 0, apdu.mData, 0, mData.length); + apdu.mLe = mLe; + apdu.mLeUsed = mLeUsed; + apdu.tlvList = new TLVList(tlvList); + return apdu; + } +} diff --git a/app/src/main/java/com/tangem/cardReader/CustomCardReader.java b/app/src/main/java/com/tangem/cardReader/CustomCardReader.java new file mode 100644 index 0000000000..9c2f9437b6 --- /dev/null +++ b/app/src/main/java/com/tangem/cardReader/CustomCardReader.java @@ -0,0 +1,86 @@ +package com.tangem.cardReader; + +import android.content.Context; +import android.nfc.TagLostException; +import android.nfc.tech.IsoDep; + +import java.io.IOException; + +public class CustomCardReader implements Runnable { + + protected NfcManager mNfcManager; + + public interface UiCallbacks { + // display console messages + void onMessageSend(String raw, String name); + void onMessageRcv(String raw, String name); + void onOkay(String message); + void onError(String message); + void onStart(String message); + void onAction(String message, String name); + void onSeparator(); + + // clear console messages + void clearMessages(); + + // ui listeners + void setUserSelectListener(UiListener callback); + + // cleanup, if needed + void onFinish(boolean err); + } + + public interface UiListener { + void onUserSelect(String aid); + } + + public static final int SW_NO_ERROR = 0x9000; + public static final int SW_GET_RESPONSE = 0x6700; + + protected IsoDep mIsoDep; + protected UiCallbacks mUiCallbacks; + + protected String mAid; + protected byte[] mAidBytes; + + protected Context mContext; + + public CustomCardReader(Context context, NfcManager manager, IsoDep isoDep, String aid, UiCallbacks uiCallbacks) { + this.mIsoDep = isoDep; + this.mAid = aid; + this.mAidBytes = Util.hexToBytes(aid); + this.mUiCallbacks = uiCallbacks; + this.mNfcManager = manager; + this.mContext = context; + } + + // send command APDU, get response APDU, and display HEX data to user + protected ResponseApdu sendAndRcv(CommandApdu cmdApdu) + throws TagLostException, IOException { + byte[] cmdBytes = cmdApdu.toBytes(); + String cmdStr = CommandApdu.toString(cmdBytes, cmdApdu.getLc()); + //mUiCallbacks.onMessageSend(cmdStr, cmdApdu.getCommandName()); + byte[] rsp = mIsoDep.transceive(cmdBytes); + ResponseApdu rspApdu = new ResponseApdu(rsp); + byte[] data = rspApdu.getData(); + + //mUiCallbacks.onMessageRcv(Util.bytesToHex(rsp), cmdApdu.getCommandName()); + + if (rspApdu.isParsedWithError()) { + mUiCallbacks.onError(rspApdu.getParseErroMessage()); + } + + /* + Log.d(TAG, "response APDU: " + Util.bytesToHex(rsp)); + if (data.length > 0) { + Log.d(TAG, TLVUtil.prettyPrintAPDUResponse(data)); + } + */ + return rspApdu; + } + + + @Override + public void run() { + } +} diff --git a/app/src/main/java/com/tangem/cardReader/INS.java b/app/src/main/java/com/tangem/cardReader/INS.java new file mode 100644 index 0000000000..8212ff802c --- /dev/null +++ b/app/src/main/java/com/tangem/cardReader/INS.java @@ -0,0 +1,43 @@ +package com.tangem.cardReader; + +/** + * Created by dvol on 07.03.2018. + */ +public enum INS { + Unknown(0x00), + BootROM_SOS(0x40), + BootROM_Tangem(0xF0), + Personalize(0xF1), + Read(0xF2), + VerifyCard(0xF3), + ValidateCard(0xF4), + VerifyCode(0xF5), + WriteIssuerData(0xF6), + GetIssuerData(0xF7), + CreateWallet(0xF8), + CheckWallet(0xF9), + SwapPIN(0xFA), + Sign(0xFB), + PurgeWallet(0xFC), + Activate(0xFE), + OpenSession(0xFF), + ReadBlockedData(0xE4), + CreateTestWallet(0xE0), + ExtractWalletKey(0xE1), + Test(0xE2), + Depersonalize(0xE3); + + INS(int Code) { + this.Code = Code; + } + + public int Code; + + public static INS ByCode(int Code) { + INS[] allINS = INS.values(); + for (INS i : allINS) { + if (i.Code == Code) return i; + } + return Unknown; + } +} diff --git a/app/src/main/java/com/tangem/cardReader/NFCEnableDialog.java b/app/src/main/java/com/tangem/cardReader/NFCEnableDialog.java new file mode 100644 index 0000000000..5eb2fd2413 --- /dev/null +++ b/app/src/main/java/com/tangem/cardReader/NFCEnableDialog.java @@ -0,0 +1,45 @@ +package com.tangem.cardReader; + +import android.app.Dialog; +import android.app.DialogFragment; +import android.content.DialogInterface; +import android.content.Intent; +import android.os.Bundle; +import android.provider.Settings; +import android.support.v7.app.AlertDialog; + +import com.tangem.wallet.R; + +/** + * Created by dvol on 18.02.2018. + */ + +public class NFCEnableDialog extends DialogFragment { + + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + builder.setCancelable(false) + .setIcon(R.drawable.ic_action_nfc_gray) + .setTitle(R.string.nfc_disabled) + .setMessage(R.string.enable_nfc) + .setPositiveButton(R.string.dialog_ok, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, + int id) { + // take user to wireless settings + getActivity().startActivity(new Intent(Settings.ACTION_WIRELESS_SETTINGS)); + } + }) + .setNegativeButton(R.string.dialog_quit, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, + int id) { + dialog.cancel(); + getActivity().finish(); + } + }); + return builder.create(); + } +} diff --git a/app/src/main/java/com/tangem/cardReader/NfcManager.java b/app/src/main/java/com/tangem/cardReader/NfcManager.java new file mode 100644 index 0000000000..7debf041aa --- /dev/null +++ b/app/src/main/java/com/tangem/cardReader/NfcManager.java @@ -0,0 +1,153 @@ +package com.tangem.cardReader; + +import android.Manifest; +import android.annotation.TargetApi; +import android.app.Activity; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.PackageManager; +import android.nfc.NfcAdapter; +import android.nfc.Tag; +import android.nfc.tech.IsoDep; +import android.os.Build; +import android.os.Bundle; +import android.support.v4.app.ActivityCompat; +import android.util.Log; + +import java.io.IOException; + +public class NfcManager { + + private static final String TAG = "NfcManager"; + + + // reader mode flags: listen for type A (not B), skipping ndef check + private static final int READER_FLAGS = NfcAdapter.FLAG_READER_NFC_A | NfcAdapter.FLAG_READER_SKIP_NDEF_CHECK | NfcAdapter.FLAG_READER_NO_PLATFORM_SOUNDS; + + private NfcAdapter mNfcAdapter; + private NFCEnableDialog mEnableNfcDialog; + private Activity mActivity; + private NfcAdapter.ReaderCallback mReaderCallback; + + private boolean broadcomWorkaround = false; + private static final int DELAY_PRESENCE = 1500; + + public NfcManager(Activity activity, NfcAdapter.ReaderCallback readerCallback) { + mActivity = activity; + mReaderCallback = readerCallback; + mNfcAdapter = NfcAdapter.getDefaultAdapter(activity); + } + + public void onResume() { + // register broadcast receiver + IntentFilter filter = new IntentFilter(NfcAdapter.ACTION_ADAPTER_STATE_CHANGED); + mActivity.registerReceiver(mBroadcastReceiver, filter); + + if (mNfcAdapter == null || !mNfcAdapter.isEnabled()) { + ShowNFCEnableDialog(); + + } else { + enableReaderMode(); + } + } + + public void ShowNFCEnableDialog() { + mEnableNfcDialog=new NFCEnableDialog(); + mEnableNfcDialog.show(mActivity.getFragmentManager(),"NFCEnableDialog"); + } + + public void onPause() { + mActivity.unregisterReceiver(mBroadcastReceiver); + disableReaderMode(); + } + + public void onStop() { + if (mEnableNfcDialog != null) { + mEnableNfcDialog.dismiss(); + } + } + + public void IgnoreTag(Tag tag) throws IOException { +// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { +// mNfcAdapter.ignore(tag, 500, null, null); +// }else{ + IsoDep isoDep = IsoDep.get(tag); + if (isoDep != null) { + isoDep.close(); + } +// } + } + + private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { + @SuppressWarnings("deprecation") + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (action == null) + return; + if (action.equals(NfcAdapter.ACTION_ADAPTER_STATE_CHANGED)) { + int state = intent.getIntExtra(NfcAdapter.EXTRA_ADAPTER_STATE, + NfcAdapter.STATE_ON); + if (state == NfcAdapter.STATE_ON + || state == NfcAdapter.STATE_TURNING_ON) { + Log.d(TAG, "state: " + state + " , dialog: " + + mEnableNfcDialog); + if (mEnableNfcDialog != null) { + mEnableNfcDialog.dismiss(); + } + if (state == NfcAdapter.STATE_ON) { + enableReaderMode(); + } + } else { + if (mEnableNfcDialog == null || !mEnableNfcDialog.isVisible()) { + ShowNFCEnableDialog(); + } + } + } + } + }; + + @TargetApi(Build.VERSION_CODES.KITKAT) + private void enableReaderMode() { + Bundle options = new Bundle(); + if (broadcomWorkaround) { + /* This is a work around for some Broadcom chipsets that does + * the presence check by sending commands that interrupt the + * processing of the ongoing command. + */ + options.putInt(NfcAdapter.EXTRA_READER_PRESENCE_CHECK_DELAY, DELAY_PRESENCE); + } + mNfcAdapter.enableReaderMode(mActivity, mReaderCallback, READER_FLAGS, options); + } + + @TargetApi(Build.VERSION_CODES.KITKAT) + private void disableReaderMode() { + if (mNfcAdapter != null) { + mNfcAdapter.disableReaderMode(mActivity); + } + } + + private static final int REQUEST_NFC_PERMISSIONS = 1; + private static String[] PERMISSIONS_NFC = { + Manifest.permission.NFC + }; + + //Checks if the app has NFC permission + //If the app does not has permission then the user will be prompted to grant permissions + public static void verifyPermissions(Activity activity) { + // Check if we have write permission + int permission = ActivityCompat.checkSelfPermission(activity, Manifest.permission.NFC); + + if (permission != PackageManager.PERMISSION_GRANTED) { + // We don't have permission so prompt the user + ActivityCompat.requestPermissions( + activity, + PERMISSIONS_NFC, + REQUEST_NFC_PERMISSIONS + ); + } + } +} + diff --git a/app/src/main/java/com/tangem/cardReader/PBKDF2.java b/app/src/main/java/com/tangem/cardReader/PBKDF2.java new file mode 100644 index 0000000000..0db1ddb69d --- /dev/null +++ b/app/src/main/java/com/tangem/cardReader/PBKDF2.java @@ -0,0 +1,103 @@ + +package com.tangem.cardReader; + +import org.spongycastle.crypto.CipherParameters; +import org.spongycastle.crypto.digests.SHA256Digest; +import org.spongycastle.crypto.macs.HMac; +import org.spongycastle.crypto.params.KeyParameter; + +import java.security.InvalidKeyException; +import java.util.Arrays; + +public final class PBKDF2 { + private static final HMac F =new HMac(new SHA256Digest()); + + /** + * Derive a key. + * + * @param password The password to derive the key from. + * @param iterations The iteration count. + * @return Returns a key derived with the specified parameters. + * @throws InvalidKeyException If the specified length for the derived key + * is to long. + */ + public static byte[] deriveKey(final byte[] password, final byte[] salt, final int iterations) throws InvalidKeyException { + return deriveKey(password, salt, iterations, F.getMacSize()); + } + + /** + * Derive a key with a specified length. + * + * @param password The password to derive the key from. + * @param iterations The iteration count. + * @param len The length of the derived key. + * @return Returns a key derived with the specified parameters. + * @throws InvalidKeyException If the specified length for the derived key + * is to long. + */ + public static byte[] deriveKey(final byte[] password, final byte[] salt, final int iterations, final int len) throws InvalidKeyException { + // Check key length + if (len > ((Math.pow(2, 32) - 1) * F.getMacSize())) + throw new InvalidKeyException("Derived key to long"); + + byte[] derivedKey = new byte[len]; + + final int J = 0; + final int K = F.getMacSize(); + final int U = F.getMacSize() << 1; + final int B = K + U; + final byte[] workingArray = new byte[K + U + 4]; + + // Initialize F + CipherParameters macParams = new KeyParameter(password); + F.init(macParams); + + // Perform iterations + for (int kpos = 0, blk = 1; kpos < len; kpos += K, blk++) { + storeInt32BE(blk, workingArray, B); + + F.update(salt, 0, salt.length); + + F.reset(); + F.update(salt, 0, salt.length); + F.update(workingArray, B, 4); + F.doFinal(workingArray, U); + System.arraycopy(workingArray, U, workingArray, J, K); + + for (int i = 1, j = J, k = K; i < iterations; i++) { + F.init(macParams); + F.update(workingArray, j, K); + F.doFinal(workingArray, k); + + for (int u = U, v = k; u < B; u++, v++) + workingArray[u] ^= workingArray[v]; + + int swp = k; + k = j; + j = swp; + } + + int tocpy = Math.min(len - kpos, K); + System.arraycopy(workingArray, U, derivedKey, kpos, tocpy); + } + + Arrays.fill(workingArray, (byte) 0); + + return derivedKey; + } + + /** + * Convert a 32-bit integer value into a big-endian byte array + * + * @param value The integer value to convert + * @param bytes The byte array to store the converted value + * @param offSet The offset in the output byte array + */ + public static void storeInt32BE(int value, byte[] bytes, int offSet) { + bytes[offSet + 3] = (byte) (value); + bytes[offSet + 2] = (byte) (value >>> 8); + bytes[offSet + 1] = (byte) (value >>> 16); + bytes[offSet] = (byte) (value >>> 24); + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/cardReader/ResponseApdu.java b/app/src/main/java/com/tangem/cardReader/ResponseApdu.java new file mode 100644 index 0000000000..2058e4d054 --- /dev/null +++ b/app/src/main/java/com/tangem/cardReader/ResponseApdu.java @@ -0,0 +1,125 @@ +package com.tangem.cardReader; + +import java.io.ByteArrayInputStream; +import java.util.Arrays; + +public class ResponseApdu { + + private int mSw1 = 0x00; + private int mSw2 = 0x00; + + private byte[] mData = new byte[0]; + private byte[] mBytes = new byte[0]; + + private TLVList tlvList = new TLVList(); + + private String parseError = null; + + private ResponseApdu() { + } + + ResponseApdu(byte[] respApdu) { + if (respApdu.length < 2) { + return; + } + if (respApdu.length > 2) { + mData = new byte[respApdu.length - 2]; + System.arraycopy(respApdu, 0, mData, 0, respApdu.length - 2); + + try { + tlvList = TLVList.fromBytes(mData); + } catch (TLVException e) { + parseError = e.getMessage(); + } + + } + mSw1 = 0x00FF & respApdu[respApdu.length - 2]; + mSw2 = 0x00FF & respApdu[respApdu.length - 1]; + mBytes = respApdu; + } + + public static ResponseApdu Decrypt(byte[] data, byte[] key) throws Exception{ + + if( data.length==2 ) + { + ResponseApdu responseApdu = new ResponseApdu(); + responseApdu.mSw1 = ((int) data[0] & 0xFF); + responseApdu.mSw2 = ((int) data[1] & 0xFF); + return responseApdu; + }else if( data.length>=18 ){ + byte[] decryptedData = CardCrypto.Decrypt(key, Arrays.copyOfRange(data, 0, data.length - 2)); + + ByteArrayInputStream inputStream = new ByteArrayInputStream(decryptedData); + byte[] baLength = new byte[2]; + inputStream.read(baLength); + int length = ((int) baLength[0] & 0xFF) * 256 + ((int) baLength[1] & 0xFF); + if (length > decryptedData.length - 4) + throw new Exception("Can't decrypt - data size invalid"); + byte[] baCRC = new byte[2]; + inputStream.read(baCRC); + byte[] answerData = new byte[length]; + inputStream.read(answerData); + byte[] crc = Util.calculateCRC16(answerData); + if (!Arrays.equals(baCRC, crc)) throw new Exception("Can't decrypt - crc invalid"); + + ResponseApdu responseApdu = new ResponseApdu(); + responseApdu.mSw1 = ((int) data[data.length - 2] & 0xFF); + responseApdu.mSw2 = ((int) data[data.length - 1] & 0xFF); + responseApdu.mBytes = data; + responseApdu.mData = answerData; + + try { + responseApdu.tlvList = TLVList.fromBytes(answerData); + } catch (TLVException e) { + responseApdu.parseError = e.getMessage(); + } + + return responseApdu; + }else{ + throw new Exception("Can't decrypt - data size to small"); + } + } + + + public int getSW1() { + return mSw1; + } + + public int getSW2() { + return mSw2; + } + + public int getSW1SW2() { + return (mSw1 << 8) | mSw2; + } + + public byte[] getData() { + return mData; + } + + public TLVList getTLVs() { + return tlvList; + } + + public boolean isParsedWithError() { + return parseError != null; + } + public String getParseErroMessage() { + return parseError; + } + public byte[] toBytes() { + return mBytes; + } + + public boolean isStatus(int sw1sw2) { + if (getSW1SW2() == sw1sw2) { + return true; + } else { + return false; + } + } + + public String getSW1SW2Description() { + return SW.getDescription(getSW1SW2()); + } +} diff --git a/app/src/main/java/com/tangem/cardReader/SW.java b/app/src/main/java/com/tangem/cardReader/SW.java new file mode 100644 index 0000000000..395d6361b9 --- /dev/null +++ b/app/src/main/java/com/tangem/cardReader/SW.java @@ -0,0 +1,43 @@ +package com.tangem.cardReader; + +/** + * Created by dvol on 07.03.2018. + */ + +public class SW { + public static final int PROCESS_COMPLETED = 0x9000; + public static final int INVALID_PARAMS = 0x6A86; + public static final int ERROR_PROCESSING_COMMAND = 0x6286; + public static final int INVALID_STATE = 0x6985; + public static final int PINS_NOT_CHANGED = PROCESS_COMPLETED; + public static final int PIN1_CHANGED = PROCESS_COMPLETED + 0x0001; + public static final int PIN2_CHANGED = PROCESS_COMPLETED + 0x0002; + public static final int PINS_CHANGED = PROCESS_COMPLETED + 0x0003; + public static final int INS_NOT_SUPPORTED = 0x6D00; + public static final int NEED_ENCRYPTION = 0x6982; + public static final int NEED_PAUSE = 0x9789; + + public static String getDescription(int sw) { + switch (sw) { + case ERROR_PROCESSING_COMMAND: + return "SW_ERROR_PROCESSING_COMMAND"; + case INVALID_PARAMS: + return "SW_INVALID_PARAMS"; + case INVALID_STATE: + return "SW_INVALID_STATE"; + case INS_NOT_SUPPORTED: + return "SW_INS_NOT_SUPPORTED"; + case NEED_ENCRYPTION: + return "SW_NEED_ENCRYPTION"; + case PIN1_CHANGED: + return "SW_PIN1_CHANGED"; + case PIN2_CHANGED: + return "SW_PIN2_CHANGED"; + case PINS_CHANGED: + return "SW_PINS_CHANGED"; + case PROCESS_COMPLETED: + return "SW_PROCESS_COMPLETED"; + } + return "???"; + } +} diff --git a/app/src/main/java/com/tangem/cardReader/SettingsMask.java b/app/src/main/java/com/tangem/cardReader/SettingsMask.java new file mode 100644 index 0000000000..973ccc5544 --- /dev/null +++ b/app/src/main/java/com/tangem/cardReader/SettingsMask.java @@ -0,0 +1,25 @@ +package com.tangem.cardReader; + +/** + * Created by dvol on 07.03.2018. + */ + +public class SettingsMask { + public static final int IsReusable = 0x0001; + public static final int UseActivation = 0x0002; + public static final int UseBlock = 0x0008; + + public static final int AllowSwapPIN = 0x0010; + public static final int AllowSwapPIN2 = 0x0020; + public static final int UseCVC = 0x0040; + public static final int ForbidDefaultPIN = 0x0080; + + public static final int UseOneCommandAtTime = 0x0100; + public static final int UseNDEF = 0x0200; + public static final int UseDynamicNDEF = 0x0400; + public static final int SmartSecurityDelay = 0x0800; + + public static final int Protocol_AllowUnencrypted = 0x1000; + public static final int Protocol_AllowStaticEncryption = 0x2000; + +} diff --git a/app/src/main/java/com/tangem/cardReader/TLV.java b/app/src/main/java/com/tangem/cardReader/TLV.java new file mode 100644 index 0000000000..0a6e398f9e --- /dev/null +++ b/app/src/main/java/com/tangem/cardReader/TLV.java @@ -0,0 +1,222 @@ +package com.tangem.cardReader; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.nio.charset.Charset; +import java.util.Arrays; + +/** + * Created by dvol on 23.06.2017. + */ + +public class TLV { + public enum Tag { + TAG_Unknown(0x00), + TAG_CardID(0x01), + TAG_Status(0x02), + TAG_CardPublicKey(0x03), + TAG_CardSignature(0x04), + TAG_CurveID(0x05), + TAG_HashAlgID(0x06), + TAG_SigningMethod(0x07), + TAG_MaxSignatures(0x08), + TAG_PauseBeforePIN2(0x09), + TAG_SettingsMask(0x0A), + TAG_CardData(0x0C), + TAG_NDEFData(0x0D), + TAG_CreateWalletAtPersonalize(0x0E), + TAG_Health(0x0F), + + TAG_PIN(0x10), + TAG_PIN2(0x11), + TAG_NewPIN(0x12), + TAG_NewPIN2(0x13), + TAG_NewPIN_Hash(0x14), + TAG_NewPIN2_Hash(0x15), + TAG_Challenge(0x16), + TAG_Salt(0x17), + TAG_ValidationCounter(0x18), + TAG_CVC(0x19), + + TAG_Session_Key_A(0x1A), + TAG_Session_Key_B(0x1B), + TAG_Pause(0x1C), + + TAG_Manufacture_ID(0x20), + TAG_Manufacturer_Signature(0x21), + + TAG_Issuer_Data_PublicKey(0x30), + TAG_Issuer_Transaction_PublicKey(0x31), + TAG_Issuer_Data(0x32), + TAG_Issuer_Data_Signature(0x33), + TAG_Issuer_Transaction_Signature(0x34), + + TAG_IsActivated(0x3A), + TAG_ActivationSeed(0x3B), + TAG_ResetPIN(0x36), + + TAG_CodePageAddress(0x40), + TAG_CodePageCount(0x41), + TAG_CodeHash(0x42), + + TAG_TrOut_Hash(0x50), + TAG_TrOut_HashSize(0x51), + TAG_TrOut_Raw(0x52), + + TAG_Wallet_PublicKey(0x60), + TAG_Signature(0x61), + TAG_RemainingSignatures(0x62), + TAG_SignedHashes(0x63), + + TAG_Wallet_PrivateKey(0x70), + TAG_Card_PrivateKey(0x71), + TAG_Block_Reason(0x72), + + + TAG_Firmware(0x80), + TAG_Batch(0x81), + TAG_ManufactureDateTime(0x82), + TAG_Issuer_ID(0x83), + TAG_Blockchain_ID(0x84), + TAG_Manufacturer_PublicKey(0x85), + TAG_CardID_Manufacturer_Signature(0x86), + + TAG_Token_Symbol(0xA0), + TAG_Token_Contract_Address(0xA1), + TAG_Token_Decimal(0xA2), + TAG_Denomination(/*0xC0*/0xee), //TODO: quick fix + TAG_ValidatedBalance(0xC1), + TAG_LastSign_Date(0xC2); + + + Tag(int Code) { + this.Code = Code; + } + + public int getCode() { + return Code; + } + + private int Code; + + public static Tag ByCode(int Code) { + Tag[] allTags = Tag.values(); + for (Tag t : allTags) if (t.getCode() == Code) return t; + return TAG_Unknown; + } + } + + private Tag tag; + + public Tag getTag() { + return tag; + } + + public byte[] Value; + + public TLV(Tag tag, byte[] value) { + this.tag = tag; + this.Value = value; + } + + public void WriteToStream(ByteArrayOutputStream stream) throws IOException { + stream.write(tag.getCode()); + if (Value != null) { + if (Value.length > 0xFE) { + stream.write(0xFF); + stream.write((Value.length >> 8) & 0xFF); + stream.write(Value.length & 0xFF); + } else { + stream.write(Value.length & 0xFF); + } + stream.write(Value); + } else { + stream.write(0x00); + } + } + + public static TLV ReadFromStream(ByteArrayInputStream stream) throws IOException { + int code = stream.read(); + if (code == -1) return null; + int len = stream.read(); + if (len == -1) + throw new IOException("Can't read TLV"); + if (len == 0xFF) { + int lenH = stream.read(); + if (lenH == -1) + throw new IOException("Can't read TLV"); + len = stream.read(); + if (len == -1) + throw new IOException("Can't read TLV"); + len |= (lenH << 8); + } + byte[] value = new byte[len]; + if (len > 0) { + if (len != stream.read(value)) { + throw new IOException("Can't read TLV"); + } + } + Tag tag = Tag.ByCode(code); + TLV result = new TLV(tag, value); + return result; + } + + public int getAsInt() { + return Util.byteArrayToInt(Value); + } + + public String getAsHexString() { + return Util.bytesToHex(Value); + } + + public String getAsString() { + //String s=String.valueOf(Value); + if (Value[Value.length - 1] == 0) { + String s1 = new String(Arrays.copyOfRange(Value, 0, Value.length - 1), Charset.forName("utf-8")); + return s1.trim(); + } else { + String s1 = new String(Value, Charset.forName("utf-8")); + return s1.trim(); + + } + } + + @Override + public String toString() { + switch (tag) { + case TAG_CardData: + case TAG_Issuer_Data: { + try { + TLVList tlvSub = TLVList.fromBytes(Value); + return String.format("%s[%d]: %s (%s)", tag.name(), Value.length, Util.bytesToHex(Value), tlvSub.toString()); + } catch (TLVException e) { + e.printStackTrace(); + } + if (Value != null) { + return String.format("%s[%d]: %s (non TLV)", tag.name(), Value.length, Util.bytesToHex(Value)); + } else { + return String.format("%s[]: [[NULL]]", tag.name()); + } + } + case TAG_CurveID: + case TAG_HashAlgID: + case TAG_Blockchain_ID: + case TAG_Manufacture_ID: + case TAG_Firmware: + case TAG_Issuer_ID: + case TAG_Token_Symbol: + if (Value != null) { + return String.format("%s[%d]: %s(%s)", tag.name(), Value.length, Util.bytesToHex(Value), getAsString()); + } else { + return String.format("%s[]: [[NULL]]", tag.name()); + } + default: + if (Value != null) { + return String.format("%s[%d]: %s", tag.name(), Value.length, Util.bytesToHex(Value)); + } else { + return String.format("%s[]: [[NULL]]", tag.name()); + } + } + } +} diff --git a/app/src/main/java/com/tangem/cardReader/TLVException.java b/app/src/main/java/com/tangem/cardReader/TLVException.java new file mode 100644 index 0000000000..a213dd71aa --- /dev/null +++ b/app/src/main/java/com/tangem/cardReader/TLVException.java @@ -0,0 +1,18 @@ +package com.tangem.cardReader; + +public class TLVException extends Exception { + + private static final long serialVersionUID = 1L; + + public TLVException(String message){ + super(message); + } + + public TLVException(String message, Throwable cause) { + super(message, cause); + } + + public TLVException(Throwable cause) { + super(cause); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/cardReader/TLVList.java b/app/src/main/java/com/tangem/cardReader/TLVList.java new file mode 100644 index 0000000000..59ba0fdc2f --- /dev/null +++ b/app/src/main/java/com/tangem/cardReader/TLVList.java @@ -0,0 +1,72 @@ +package com.tangem.cardReader; + +/** + * Created by dvol on 23.06.2017. + */ + +import android.support.annotation.NonNull; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; + +public class TLVList extends ArrayList { + public String getParsedTLVs(String Prefix) { + String parsed = ""; + for (int i = 0; i < size(); i++) { + parsed += Prefix + this.get(i).toString() + (i < size() - 1 ? "\n" : ""); + } + return parsed;//.substring(0,parsed.length()-2); + } + + public TLVList() { + super(); + } + + public TLVList(@NonNull Collection c) { + super(c); + } + + public TLV getTLV(TLV.Tag tag) { + for (TLV tlv : this) { + if (tlv.getTag() == tag) return tlv; + } + return null; + } + + public int getTagAsInt(TLV.Tag tag) { + TLV tlv = getTLV(tag); + return Util.byteArrayToInt(tlv.Value); + } + + public byte[] toBytes() { + ByteArrayOutputStream stream = new ByteArrayOutputStream(); + for (TLV tlv : this) { + try { + tlv.WriteToStream(stream); + } catch (IOException e) { + e.printStackTrace(); + break; + } + } + return stream.toByteArray(); + } + + public static TLVList fromBytes(byte[] mData) throws TLVException { + TLVList tlvList = new TLVList(); + ByteArrayInputStream stream = new ByteArrayInputStream(mData); + TLV tlv = null; + do { + try { + tlv = TLV.ReadFromStream(stream); + if (tlv != null) tlvList.add(tlv); + } catch (IOException e) { + throw new TLVException("TLVError: " + e.getMessage()); + } + } + while (tlv != null); + return tlvList; + } +} diff --git a/app/src/main/java/com/tangem/cardReader/Util.java b/app/src/main/java/com/tangem/cardReader/Util.java new file mode 100644 index 0000000000..79a0205746 --- /dev/null +++ b/app/src/main/java/com/tangem/cardReader/Util.java @@ -0,0 +1,959 @@ +package com.tangem.cardReader; + +import android.text.format.DateUtils; + +import org.spongycastle.crypto.digests.RIPEMD160Digest; + +import java.io.BufferedWriter; +import java.io.ByteArrayOutputStream; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.math.BigInteger; +import java.nio.charset.Charset; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.SecureRandom; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.BitSet; +import java.util.Date; +import java.util.Locale; +import java.util.StringTokenizer; + +public class Util { + + public static String getSpaces(int length) { + StringBuilder buf = new StringBuilder(length); + + for (int i = 0; i < length; i++) { + buf.append(" "); + } + + return buf.toString(); + } + + public static String prettyPrintHex(String in, int indent, boolean wrapLines) { + StringBuilder buf = new StringBuilder(); + + for (int i = 0; i < in.length(); i++) { + char c = in.charAt(i); + buf.append(c); + + int nextPos = i+1; + if (wrapLines && nextPos % 32 == 0 && nextPos != in.length()) { + buf.append("\n").append(getSpaces(indent)); + } else if (nextPos % 2 == 0 && nextPos != in.length()) { + //buf.append(" "); + } + } + return buf.toString(); + } + + public static String prettyPrintHex(String in, int indent){ + return prettyPrintHex(in, indent, true); + } + + public static String prettyPrintHex(byte[] data, int indent) { + return Util.prettyPrintHex(Util.byteArrayToHexString(data), indent, true); + } + + public static String prettyPrintHex(byte[] data) { + return Util.prettyPrintHex(Util.byteArrayToHexString(data), 0, true); + } + + public static String prettyPrintHex(byte[] data, int startPos, int length) { + return Util.prettyPrintHex(Util.byteArrayToHexString(data, startPos, length), 0, true); + } + + public static String prettyPrintHexNoWrap(byte[] data) { + return Util.prettyPrintHex(Util.byteArrayToHexString(data), 0, false); + } + + public static String prettyPrintHexNoWrap(byte[] data, int startPos, int length) { + return Util.prettyPrintHex(Util.byteArrayToHexString(data, startPos, length), 0, false); + } + + public static String prettyPrintHexNoWrap(String in) { + return Util.prettyPrintHex(in, 0, false); + } + + public static String prettyPrintHex(String in) { + return prettyPrintHex(in, 0, true); + } + + public static String prettyPrintHex(BigInteger bi) { + byte[] data = bi.toByteArray(); + if (data[0] == (byte) 0x00) { + byte[] tmp = new byte[data.length - 1]; + System.arraycopy(data, 1, tmp, 0, data.length - 1); + data = tmp; + } + return prettyPrintHex(data); + } + + public static byte[] performRSA(byte[] dataBytes, byte[] expBytes, byte[] modBytes) { + + int inBytesLength = dataBytes.length; + + if (expBytes[0] >= (byte) 0x80) { + //Prepend 0x00 to modulus + byte[] tmp = new byte[expBytes.length + 1]; + tmp[0] = (byte) 0x00; + System.arraycopy(expBytes, 0, tmp, 1, expBytes.length); + expBytes = tmp; + } + + if (modBytes[0] >= (byte) 0x80) { + //Prepend 0x00 to modulus + byte[] tmp = new byte[modBytes.length + 1]; + tmp[0] = (byte) 0x00; + System.arraycopy(modBytes, 0, tmp, 1, modBytes.length); + modBytes = tmp; + } + + if (dataBytes[0] >= (byte) 0x80) { + //Prepend 0x00 to signed data to avoid that the most significant bit is interpreted as the "signed" bit + byte[] tmp = new byte[dataBytes.length + 1]; + tmp[0] = (byte) 0x00; + System.arraycopy(dataBytes, 0, tmp, 1, dataBytes.length); + dataBytes = tmp; + } + + BigInteger exp = new BigInteger(expBytes); + BigInteger mod = new BigInteger(modBytes); + BigInteger data = new BigInteger(dataBytes); + + byte[] result = data.modPow(exp, mod).toByteArray(); + + if (result.length == (inBytesLength+1) && result[0] == (byte)0x00) { + //Remove 0x00 from beginning of array + byte[] tmp = new byte[inBytesLength]; + System.arraycopy(result, 1, tmp, 0, inBytesLength); + result = tmp; + } + + return result; + } + + public static byte[] calculateSHA1(byte[] data) throws NoSuchAlgorithmException { + MessageDigest sha1 = MessageDigest.getInstance("SHA-1"); + return sha1.digest(data); + } + + public static byte[] calculateSHA224(byte[] data) throws NoSuchAlgorithmException { + MessageDigest sha = MessageDigest.getInstance("SHA-224"); + return sha.digest(data); + } + + public static byte[] calculateSHA256(byte[] data) throws NoSuchAlgorithmException { + MessageDigest sha256 = MessageDigest.getInstance("SHA-256"); + return sha256.digest(data); + } + + public static byte[] calculateSHA384(byte[] data) throws NoSuchAlgorithmException { + MessageDigest sha = MessageDigest.getInstance("SHA-384"); + return sha.digest(data); + } + + public static byte[] calculateSHA512(byte[] data) throws NoSuchAlgorithmException { + MessageDigest sha = MessageDigest.getInstance("SHA-512"); + return sha.digest(data); + } + + public static byte[] calculateSHA256(String Message) throws NoSuchAlgorithmException { + MessageDigest sha256 = MessageDigest.getInstance("SHA-256"); + byte data[]=Message.getBytes(Charset.forName("UTF-8")); + return sha256.digest(data); + } + + public static byte[] calculateRIPEMD160(byte[] data) throws NoSuchAlgorithmException, NoSuchProviderException { + //MessageDigest hashAlg = MessageDigest.getInstance("RIPEMD-160", "SC"); + //return hashAlg.digest(data); + + RIPEMD160Digest digest = new RIPEMD160Digest(); + digest.update(data, 0, data.length); + byte[] out = new byte[20]; + digest.doFinal(out, 0); + return out; + } + + public static String byte2Hex(byte b) { + String[] HEX_DIGITS = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f"}; + int nb = b & 0xFF; + int i_1 = (nb >>> 4) & 0xF; + int i_2 = nb & 0xF; + return HEX_DIGITS[i_1] + HEX_DIGITS[i_2]; + } + + public static String short2Hex(short s) { + byte b1 = (byte) (s >>> 8); + byte b2 = (byte) (s & 0xFF); + return byte2Hex(b1) + byte2Hex(b2); + } + + public static int byteToInt(byte b) { + return (int) b & 0xFF; + } + + public static int byteToInt(byte first, byte second) { + int value = (first & 0xFF) << 8; + value += second & 0xFF; + return value; + } + + public static short byte2Short(byte b1, byte b2) { + return (short) ((b1 << 8) | (b2 & 0xFF)); + } + + public static String getFormattedNanoTime(long nano) { + StringBuilder buf = new StringBuilder(); + buf.append((int) (nano / 1000000)); + buf.append("ms "); + buf.append(nano % 1000000); + buf.append("ns"); + return buf.toString(); + } + + + public static String formatDate(Date date) + { + return DateUtils.formatDateTime(null, date.getTime(), DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_NUMERIC_DATE | DateUtils.FORMAT_SHOW_YEAR); +// return DateFormat.getDateInstance(DateFormat.SHORT).format(date); + } + + public static String formatDateTime(Date date) + { + return formatDate(date)+" "+formatTime(date); + } + + public static String formatTime(Date date) + { + return new SimpleDateFormat("HH:mm:ss").format(date); +// DateFormat.getTimeInstance(DateFormat.MEDIUM).format(date) +// return DateUtils.formatDateTime(null, date.getTime(), DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_24HOUR);//DateFormat.getTimeInstance(DateFormat.MEDIUM).format(date); + } + + public static byte[] getCurrentDateAsNumericEncodedByteArray(){ + SimpleDateFormat format = new SimpleDateFormat("yyMMdd", Locale.US); + return fromHexString(format.format(new Date())); + } + + //This prints all non-control characters common to all parts of ISO/IEC 8859 + //See EMV book 4 Annex B: Table 36: Common Character Set + public static String getSafePrintChars(byte[] byteArray) { + if (byteArray == null) { + return ""; +// throw new IllegalArgumentException("Argument 'byteArray' cannot be null"); + } + return getSafePrintChars(byteArray, 0, byteArray.length); + } + + public static String getSafePrintChars(byte[] byteArray, int startPos, int length) { + if (byteArray == null) { + return ""; +// throw new IllegalArgumentException("Argument 'byteArray' cannot be null"); + } + if(byteArray.length < startPos+length){ + throw new IllegalArgumentException("startPos("+startPos+")+length("+length+") > byteArray.length("+byteArray.length+")"); + } + StringBuilder buf = new StringBuilder(); + for (int i = startPos; i < startPos+length; i++) { + if (byteArray[i] >= (byte) 0x20 && byteArray[i] < (byte) 0x7F) { + buf.append((char) byteArray[i]); + } else { + buf.append("."); + } + } + return buf.toString(); + } + + public static byte[] hexToBytes(String str) { + byte[] bytes = new byte[str.length() / 2]; + for (int i = 0; i < bytes.length; i++) { + bytes[i] = (byte) Integer.parseInt(str.substring(2 * i, 2 * i + 2), + 16); + } + return bytes; + } + + final private static char[] hexArray = "0123456789ABCDEF".toCharArray(); + + public static String bytesToHex(byte[] bytes) { + if( bytes==null ) return "[EMPTY]"; + char[] hexChars = new char[bytes.length * 2]; + for (int j = 0; j < bytes.length; j++) { + int v = bytes[j] & 0xFF; + hexChars[j * 2] = hexArray[v >>> 4]; + hexChars[j * 2 + 1] = hexArray[v & 0x0F]; + } + return new String(hexChars); + } + + /** + * Converts a byte array into a hex string. + * @param byteArray the byte array source + * @return a hex string representing the byte array + */ + public static String byteArrayToHexString(final byte[] byteArray) { + if (byteArray == null) { + return ""; + } + return byteArrayToHexString(byteArray, 0, byteArray.length); + } + + public static String byteArrayToHexString(final byte[] byteArray, int startPos, int length) { + if (byteArray == null) { + return ""; + } + if(byteArray.length < startPos+length){ + throw new IllegalArgumentException("startPos("+startPos+")+length("+length+") > byteArray.length("+byteArray.length+")"); + } +// int readBytes = byteArray.length; + StringBuilder hexData = new StringBuilder(); + int onebyte; + for (int i = 0; i < length; i++) { + onebyte = ((0x000000ff & byteArray[startPos+i]) | 0xffffff00); + hexData.append(Integer.toHexString(onebyte).substring(6)); + } + return hexData.toString(); + } + + public static String int2Hex(int i) { + String hex = Integer.toHexString(i); + if (hex.length() % 2 != 0) { + hex = "0" + hex; + } + return hex; + } + + public static String int2HexZeroPad(int i) { + String hex = int2Hex(i); + if (hex.length() % 2 != 0) { + hex = "0" + hex; + } + return hex; + } + /** + * The length of the returned array depends on the size of the int + * @param value + * @return + */ + public static byte[] intToByteArray(int value) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + + byte one = (byte) (value >>> 24); + byte two = (byte) (value >>> 16); + byte three = (byte) (value >>> 8); + byte four = (byte) (value); + + boolean found = false; + + if (one > 0x00) { + baos.write(one); + found = true; + } + if (found || two > 0x00) { + baos.write(two); + found = true; + } + + if (found || three > 0x00) { + baos.write(three); + } + + baos.write(four); + + return baos.toByteArray(); + } + + /** + * Returns a byte array with length = 2 + * @param value + * @return + */ + public static byte[] intToByteArray2(int value) { + return new byte[]{ + (byte) (value >>> 8), + (byte) value}; + } + + /** + * Returns a byte array with length = 4 + * @param value + * @return + */ + public static byte[] intToByteArray4(int value) { + return new byte[]{ + (byte) (value >>> 24), + (byte) (value >>> 16), + (byte) (value >>> 8), + (byte) value}; + } + + public static byte[] longToByteArray8(long value) { + return new byte[]{ + (byte) (value >>> 56), + (byte) (value >>> 48), + (byte) (value >>> 40), + (byte) (value >>> 32), + (byte) (value >>> 24), + (byte) (value >>> 16), + (byte) (value >>> 8), + (byte) value}; + } + + public static int byteArrayToInt(byte[] byteArray) { + if( byteArray.length==1 ) return byteArray[0]&0xFF; + java.nio.ByteBuffer BB=java.nio.ByteBuffer.wrap(byteArray); + switch (byteArray.length) + { + case 2: return BB.getShort(); + case 4: return BB.getInt(); + default: throw new IllegalArgumentException("Length must be 1,2 or 4. Length = " + byteArray.length); + } + } + + public static long byteArrayToLong(byte[] byteArray) { + if( byteArray.length==1 ) return byteArray[0]&0xFF; + java.nio.ByteBuffer BB=java.nio.ByteBuffer.wrap(byteArray); + switch (byteArray.length) + { + case 2: return BB.getShort(); + case 4: return BB.getInt(); + case 8: return BB.getLong(); + default: throw new IllegalArgumentException("Length must be 1,2,4 or 8. Length = " + byteArray.length); + } + } + + public static byte[] longToByteArray(long value) + { + return new byte[]{ + (byte) (value >>> 56), + (byte) (value >>> 48), + (byte) (value >>> 40), + (byte) (value >>> 32), + (byte) (value >>> 24), + (byte) (value >>> 16), + (byte) (value >>> 8), + (byte) value}; + } + + public static int byteArrayToInt(byte[] byteArray, int startPos, int length) { + if (byteArray == null) { + throw new IllegalArgumentException("Parameter 'byteArray' cannot be null"); + } + if (length <= 0 || length > 4) { + throw new IllegalArgumentException("Length must be between 1 and 4. Length = " + length); + } + if (length == 4 && Util.isBitSet(byteArray[startPos], 8)){ + throw new IllegalArgumentException("Signed bit is set (leftmost bit): " + Util.byte2Hex(byteArray[startPos])); + } + int value = 0; + for (int i = 0; i < length; i++) { + value += ((byteArray[startPos+i] & 0xFF) << 8 * (length - i - 1)); + } + return value; + } + + public static long byteArrayToLong(byte[] byteArray, int startPos, int length) { + if (byteArray == null) { + throw new IllegalArgumentException("Parameter 'byteArray' cannot be null"); + } + if (length <= 0 || length > 8) { + throw new IllegalArgumentException("Length must be between 1 and 4. Length = " + length); + } + if (length == 8 && Util.isBitSet(byteArray[startPos], 8)){ + throw new IllegalArgumentException("Signed bit is set (leftmost bit): " + Util.byte2Hex(byteArray[startPos])); + } + long value = 0; + for (int i = 0; i < length; i++) { + value += ((byteArray[startPos+i] & (long)0xFF) << 8 * (length - i - 1)); + } + return value; + } + + public static byte[] fromHexString(String encoded) { + encoded = removeSpaces(encoded); + if (encoded.length() == 0){ + return new byte[0]; + } + if ((encoded.length() % 2) != 0) { + throw new IllegalArgumentException("Input string must contain an even number of characters: "+encoded); + } + final byte result[] = new byte[encoded.length() / 2]; + final char enc[] = encoded.toCharArray(); + for (int i = 0; i < enc.length; i += 2) { + StringBuilder curr = new StringBuilder(2); + curr.append(enc[i]).append(enc[i + 1]); + result[i / 2] = (byte) Integer.parseInt(curr.toString(), 16); + } + return result; + } + + public static String removeCRLFTab(String s) { + StringTokenizer st = new StringTokenizer(s, "\r\n\t", false); + StringBuilder buf = new StringBuilder(); + while (st.hasMoreElements()) { + buf.append(st.nextElement()); + } + return buf.toString(); + } + + public static String removeSpaces(String s) { + return s.replaceAll(" ", ""); + } + + public static String readInputStreamToString(InputStream is, String encoding) throws IOException { + InputStreamReader input = new InputStreamReader(is, encoding); + final int CHARS_PER_PAGE = 5000; //counting spaces + final char[] buffer = new char[CHARS_PER_PAGE]; + StringBuilder output = new StringBuilder(CHARS_PER_PAGE); + for (int read = input.read(buffer, 0, buffer.length); + read != -1; + read = input.read(buffer, 0, buffer.length)) { + output.append(buffer, 0, read); + } + + String text = output.toString(); + return text; + } + + public static void writeStringToFile(String string, String fileName, boolean append) throws IOException { + BufferedWriter out = new BufferedWriter(new FileWriter(fileName, append)); + out.write(string); + out.close(); + } + + /** + * Binary Coded Decimal (BCD) + * @param val + * @return + */ + public static byte[] intToBinaryEncodedDecimalByteArray(int val){ + String str = String.valueOf(val); + if(str.length() % 2 != 0){ + str = "0"+str; + } + return Util.fromHexString(str); + } + + /** + * This method converts the literal hex representation of a byte to an int. + * eg 0x70 = 70 (int) + * @param b + */ + public static int binaryCodedDecimalToInt(byte b) { + String hex = Util.byte2Hex(b); + try { + return Integer.parseInt(hex); + } catch (NumberFormatException ex) { + throw new IllegalArgumentException("The hex representation of argument b must be digits", ex); + } + } + + /** + * This method converts the literal hex representation of a decimal + * encoded in 1-5 bytes to an int. + * The value should not be larger than Integer.MAX_VALUE + * + * eg 0x70 = 70 (decimal) + * eg 0x21 47 48 36 47 = 2147483647 (decimal) + * @param hex + */ + public static int binaryHexCodedDecimalToInt(String hex) { + if (hex == null) { + throw new IllegalArgumentException("Param hex cannot be null"); + } + hex = Util.removeSpaces(hex); + if (hex.length() > 10) { + throw new IllegalArgumentException("There must be a maximum of 5 hex octets. hex=" + hex); + } + try { + return Integer.parseInt(hex); + } catch (NumberFormatException ex) { + throw new IllegalArgumentException("Argument hex must be all digits. hex="+hex, ex); + } + } + + /** + * This method converts a 1-5 byte BCD to an int. + * eg 0x7099 = 7099 (int) + * @param bcdArray + */ + public static int binaryHexCodedDecimalToInt(byte[] bcdArray) { + if (bcdArray == null) { + throw new IllegalArgumentException("Param bcdArray cannot be null"); + } + return binaryHexCodedDecimalToInt(Util.byteArrayToHexString(bcdArray)); + } + + /** + * This returns a String with length = 8 + * @param val + * @return + */ + public static String byte2BinaryLiteral(byte val) { + String s = Integer.toBinaryString(Util.byteToInt(val)); + if (s.length() < 8) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < 8 - s.length(); i++) { + sb.append('0'); + } + sb.append(s); + s = sb.toString(); + } + return s; + } + + /** + * Returns a bitset containing the values in bytes. + * The byte-ordering of bytes must be big-endian which means the most significant bit is in element 0. + * + * @param bytes + * @return + */ + public static BitSet byteArray2BitSet(byte[] bytes) { + BitSet bits = new BitSet(); + for (int i = 0; i < bytes.length * 8; i++) { + if ((bytes[bytes.length - i / 8 - 1] & (1 << (i % 8))) > 0) { + bits.set(i); + } + } + return bits; + } + + /* Returns a byte array of at least length 1. + * The most significant bit in the result is guaranteed not to be a 1 + * (since BitSet does not support sign extension). + * The byte-ordering of the result is big-endian which means the most significant bit is in element 0. + * The bit at index 0 of the bit set is assumed to be the least significant bit. + */ + public static byte[] bitSet2ByteArray(BitSet bits) { + byte[] bytes = new byte[bits.length() / 8 + 1]; + for (int i = 0; i < bits.length(); i++) { + if (bits.get(i)) { + bytes[bytes.length - i / 8 - 1] |= 1 << (i % 8); + } + } + return bytes; + } + + /** + * + * @param val + * @param bitPos The leftmost bit is 8 (the most significant bit) + * @return + */ + public static boolean isBitSet(byte val, int bitPos) { + if (bitPos < 1 || bitPos > 8) { + throw new IllegalArgumentException("parameter 'bitPos' must be between 1 and 8. bitPos=" + bitPos); + } + if ((val >>> (bitPos - 1) & 0x1) == 1) { + return true; + } + return false; + } + +// /** +// * +// * @param val +// * @return +// */ +// public static int getBitsSetCount(byte val) { +// int numBitsSet = 0; +// for(int i=1; i<=8; i++){ +// if(Util.isBitSet(val, i)){ +// numBitsSet++; +// } +// } +// return numBitsSet; +// } + + /** + * + * @param data + * @param bitPos The leftmost bit is 8 + * @param on + * @return + */ + public static byte setBit(byte data, int bitPos, boolean on) { + if (bitPos < 1 || bitPos > 8) { + throw new IllegalArgumentException("parameter 'bitPos' must be between 1 and 8. bitPos=" + bitPos); + } + if (on) { + // set bit + return data |= 1 << (bitPos - 1); + } else { + // clear bit + return data &= ~(1 << (bitPos - 1)); + } + } + + public static byte[] generateRandomBytes(int numBytes) { + // TODO: get bytes from a hardware RNG, or set seed + byte[] rndBytes = new byte[numBytes]; + SecureRandom random = new SecureRandom(); + random.nextBytes(rndBytes); + return rndBytes; + } + + public static byte generateRandomByte() { + SecureRandom random = new SecureRandom(); + return (byte)(random.nextInt()&0xFF); + } + + + public static InputStream loadResource(Class cls, String path){ + return cls.getResourceAsStream(path); + } + + /** + * Copies the specified array, prepending 0x00, or cutting off MSBytes if necessary + * @param original + * @param newLength + * @return + */ + public static byte[] resizeArray(byte[] original, int newLength) { + if(original == null){ + throw new IllegalArgumentException("byte array cannot be null"); + } + if(newLength < 0){ + throw new IllegalArgumentException("Illegal new length: "+newLength+". Must be >= 0"); + } + if(newLength == 0){ + return new byte[0]; + } + byte[] tmp = new byte[newLength]; + + int srcPos = tmp.length > original.length ? 0 : original.length - tmp.length; + int destPos = tmp.length > original.length ? tmp.length - original.length : 0; + int length = tmp.length > original.length ? original.length : tmp.length; + + System.arraycopy(original, srcPos, tmp, destPos, length); + + return tmp; + } + + public static byte[] copyByteArray(byte[] array2Copy){ +// byte[] copy = new byte[array2Copy.length]; +// System.arraycopy(array2Copy, 0, copy, 0, array2Copy.length); +// return copy; + if (array2Copy == null) { + //return new byte[0] instead? + throw new IllegalArgumentException("Argument 'array2Copy' cannot be null"); + } + return copyByteArray(array2Copy, 0, array2Copy.length); + } + + public static byte[] copyByteArray(byte[] array2Copy, int startPos, int length){ + if (array2Copy == null) { + //return new byte[0] instead? + throw new IllegalArgumentException("Argument 'array2Copy' cannot be null"); + } + if(array2Copy.length < startPos+length){ + throw new IllegalArgumentException("startPos("+startPos+")+length("+length+") > byteArray.length("+array2Copy.length+")"); + } + byte[] copy = new byte[array2Copy.length]; + System.arraycopy(array2Copy, startPos, copy, 0, length); + return copy; + } + + public static String getStackTrace(Throwable t){ + StringWriter sw = new StringWriter(); + t.printStackTrace(new PrintWriter(sw)); + return sw.toString(); + } + + public static Class getCallerClass(int i) { + Class[] classContext = new SecurityManager() { + @Override public Class[] getClassContext() { + return super.getClassContext(); + } + }.getClassContext(); + if (classContext != null) { + for (int j = 0; j < classContext.length; j++) { + if (classContext[j] == Util.class) { + return classContext[i+j]; + } + } + } else { + // SecurityManager.getClassContext() returns null on Android 4.0 + try { + StackTraceElement[] classNames = Thread.currentThread().getStackTrace(); + for (int j = 0; j < classNames.length; j++) { + if (Class.forName(classNames[j].getClassName()) == Util.class) { + return Class.forName(classNames[i+j].getClassName()); + } + } + } catch (ClassNotFoundException e) { } + } + return null; + } + + public static String decodeOID(byte[] enc){ + StringBuilder sb = new StringBuilder(); + + //First OID Component (standard) + //0: ITU-T + //1: ISO + //2: joint-iso-itu-t + + //Second OID Component (part in a multi part standard) + //0: standard + //1: registration-authority + //2: member-body + //3: identified-organization + + + long firstSubidentifier = 0; + + int i=0; + while(Util.isBitSet(enc[i], 8)){ + firstSubidentifier = (firstSubidentifier << 7) | (enc[i] & 0x7f); + i++; + } + firstSubidentifier = (firstSubidentifier << 7) | (enc[i] & 0x7f); + i++; + + if(firstSubidentifier >= 80){ + long firstOIDComp = 2; + long secondOIDComp = firstSubidentifier - 80; + sb.append(firstOIDComp).append(".").append(secondOIDComp); + }else{ + long secondOIDComp = firstSubidentifier % 40; + long firstOIDComp = (firstSubidentifier - secondOIDComp)/40; + sb.append(firstOIDComp).append(".").append(secondOIDComp); + } + + for(; i> 8) ^ ((chBlock & 0xFF) << 8) & 0xFFFF) ^ (((chBlock & 0xFF) << 3) & 0xFFFF) ^ (((chBlock & 0xFF) >> 4) & 0xFFFF);// (wCRC>>8)^((int)chBlock<<8)^((int) chBlock<<3)^((int)chBlock>>4); + } while (i < bytes.length); + + return new byte[]{(byte) (wCRC & 0xFF), (byte) ((wCRC & 0xFFFF) >> 8)}; + } + + public static String formatDateTimeToFileName(Date date) { + return new SimpleDateFormat("yyyy_MM_dd__HH_mm_ss", Locale.US).format(date); + } +} diff --git a/app/src/main/java/com/tangem/wallet/BTCUtils.java b/app/src/main/java/com/tangem/wallet/BTCUtils.java new file mode 100644 index 0000000000..1f7986049a --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/BTCUtils.java @@ -0,0 +1,519 @@ +package com.tangem.wallet; + +/** + * Created by Ilia on 29.09.2017. + */ + +import android.util.Log; + +import com.tangem.cardReader.Util; + +import org.spongycastle.asn1.ASN1EncodableVector; +import org.spongycastle.asn1.ASN1Integer; +import org.spongycastle.asn1.DERSequence; +import org.spongycastle.asn1.DERSequenceGenerator; +import org.spongycastle.asn1.sec.SECNamedCurves; +import org.spongycastle.asn1.x9.X9ECParameters; +import org.spongycastle.asn1.x9.X9IntegerConverter; +import org.spongycastle.crypto.params.ECDomainParameters; +import org.spongycastle.crypto.params.ECPrivateKeyParameters; +import org.spongycastle.crypto.params.ECPublicKeyParameters; +import org.spongycastle.crypto.signers.ECDSASigner; +import org.spongycastle.jce.ECNamedCurveTable; +import org.spongycastle.jce.spec.ECNamedCurveParameterSpec; +import org.spongycastle.jce.spec.ECPrivateKeySpec; +import org.spongycastle.jce.spec.ECPublicKeySpec; +import org.spongycastle.math.ec.ECAlgorithms; +import org.spongycastle.math.ec.ECCurve; +import org.spongycastle.math.ec.ECPoint; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.math.BigInteger; +import java.security.InvalidKeyException; +import java.security.KeyFactory; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.PublicKey; +import java.security.Signature; +import java.security.SignatureException; +import java.security.spec.InvalidKeySpecException; +import java.text.Format; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Stack; +import java.util.regex.Pattern; + +import static org.bitcoinj.core.ECKey.CURVE; +import static org.bitcoinj.core.ECKey.HALF_CURVE_ORDER; + +@SuppressWarnings({"WeakerAccess", "TryWithIdenticalCatches", "unused"}) +public final class BTCUtils { + static final BigInteger LARGEST_PRIVATE_KEY = new BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141", 16);//SECP256K1_N + public static final long MIN_FEE_PER_KB = 10000; + public static final long MAX_ALLOWED_FEE = FormatUtil.parseValue("0.1"); + public static final long MIN_PRIORITY_FOR_NO_FEE = 57600000; + public static final long MIN_MIN_OUTPUT_VALUE_FOR_NO_FEE = 10000000L; + public static final int MAX_TX_LEN_FOR_NO_FEE = 10000; + public static final float EXPECTED_BLOCKS_PER_DAY = 144.0f;//(expected confirmations per day) + + public static long calcMinimumFee(int txLen, Collection unspentOutputInfos, long minOutput) { + if (isZeroFeeAllowed(txLen, unspentOutputInfos, minOutput)) { + return 0; + } + return MIN_FEE_PER_KB * (1 + txLen / 1000); + } + + public static boolean isZeroFeeAllowed(int txLen, Collection unspentOutputInfos, long minOutput) { + if (txLen < MAX_TX_LEN_FOR_NO_FEE && minOutput > MIN_MIN_OUTPUT_VALUE_FOR_NO_FEE) { + long priority = 0; + for (UnspentOutputInfo output : unspentOutputInfos) { + if (output.confirmations > 0) { + priority += output.confirmations * output.value; + } + } + priority /= txLen; + if (priority > MIN_PRIORITY_FOR_NO_FEE) { + return true; + } + } + return false; + } + + public static int getMaximumTxSize(Collection unspentOutputInfos, int outputsCount, boolean compressedPublicKey) throws BitcoinException { + if (unspentOutputInfos == null || unspentOutputInfos.isEmpty()) { + throw new BitcoinException(BitcoinException.ERR_NO_INPUT, "No information about tx inputs provided"); + } + int maxInputScriptLen = 73 + (compressedPublicKey ? 33 : 65); + return 9 + unspentOutputInfos.size() * (41 + maxInputScriptLen) + outputsCount * 33; + } + + public static String publicKeyToAddress(byte[] publicKey) { + return publicKeyToAddress(false, publicKey); + } + + public static String publicKeyToAddress(boolean testNet, byte[] publicKey) { + try { + byte[] hashedPublicKey = CryptoUtil.sha256ripemd160(publicKey); + byte[] addressBytes = new byte[1 + hashedPublicKey.length + 4]; + addressBytes[0] = (byte) (testNet ? 111 : 0); + System.arraycopy(hashedPublicKey, 0, addressBytes, 1, hashedPublicKey.length); + MessageDigest digestSha = MessageDigest.getInstance("SHA-256"); + digestSha.update(addressBytes, 0, addressBytes.length - 4); + byte[] check = digestSha.digest(digestSha.digest()); + System.arraycopy(check, 0, addressBytes, hashedPublicKey.length + 1, 4); + return Base58.encodeBase58(addressBytes); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + } + + public static String toHex(byte[] bytes) { + if (bytes == null) { + return ""; + } + final char[] hexArray = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; + char[] hexChars = new char[bytes.length * 2]; + int v; + for (int j = 0; j < bytes.length; j++) { + v = bytes[j] & 0xFF; + hexChars[j * 2] = hexArray[v >>> 4]; + hexChars[j * 2 + 1] = hexArray[v & 0x0F]; + } + return new String(hexChars); + } + + + public static byte[] buildTXForSign(String myAddress, String outputAddress, String changeAddress, ArrayList unspentOutputs, int currentInputPos, long amount, long change) throws BitcoinException, IOException { + byte[] myScript = Transaction.Script.buildOutput(myAddress).bytes; + unspentOutputs.get(currentInputPos).scriptForBuild = myScript; + int inputPos = currentInputPos; + byte[] body = buildBodyTX(outputAddress, changeAddress, unspentOutputs, inputPos, amount, change); + + ByteArrayOutputStream os = new ByteArrayOutputStream(); + os.write(body); + os.write(new byte[]{0x01, 0x00, 0x00, 0x00}); + byte[] tx = os.toByteArray(); + return tx; + } + + public static byte[] buildTXForSend(String outputAddress, String changeAddress, ArrayList unspentOutputs, long amount, long change) throws BitcoinException, IOException { + int inputPos = -1; + byte[] body = buildBodyTX(outputAddress, changeAddress, unspentOutputs, inputPos, amount, change); + ByteArrayOutputStream os = new ByteArrayOutputStream(); + os.write(body); + byte[] tx = os.toByteArray(); + return tx; + } + + public static byte[] buildBodyTX(String outputAddress, String changeAddress, ArrayList unspentOutputs, int inputPos, long amount, long change) throws BitcoinException, IOException { + + //0200000000 + BitcoinOutputStream forSign = new BitcoinOutputStream(); + forSign.writeInt32(0x01);//write(new byte[]{0x02, 0x00, 0x00, 0x00}); // version + + //01 + byte inputCount = (byte)unspentOutputs.size(); + forSign.write(inputCount); // input count + //hex str hash prev btc + + for(int i = 0; i < inputCount; ++i) + { + UnspentOutputInfo outPut = unspentOutputs.get(i); + int outputIndex = outPut.outputIndex; + byte[] txHash = BTCUtils.reverse(Util.hexToBytes(outPut.txHashForBuild));//Sha256Hash.hash(rawTxByte); + forSign.write(txHash); + forSign.writeInt32(outputIndex); //output index in prev tx + if(inputPos ==-1 || i == inputPos) + { + // hex str 1976a914....88ac + forSign.write((byte)outPut.scriptForBuild.length); + forSign.write(outPut.scriptForBuild); + } + else + { + forSign.write(0x00); + } + //ffffffff + forSign.write(new byte[]{(byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff}); // sequence + } + + + //02 + byte outputCount = (byte)((change==0) ? 1 : 2); // outputCount + forSign.write(outputCount); + + //8 bytes + + forSign.writeInt64(amount); //amount + byte[] sendScript = Transaction.Script.buildOutput(outputAddress).bytes; // build out + //hex str 1976a914....88ac + forSign.write((byte)sendScript.length); + forSign.write(sendScript); + + if(change!=0){ + //8 bytes + forSign.writeInt64(change); // change + //hex str 1976a914....88ac + byte[] chancheScript = Transaction.Script.buildOutput(changeAddress).bytes; //build out + forSign.write((byte)chancheScript.length); + forSign.write(chancheScript); + + } + + //00000000 + forSign.write(new byte[]{0x00, 0x00, 0x00, 0x00}); + + //forSign.write(new byte[]{0x01, 0x00, 0x00, 0x00}); + + byte[] rawData = forSign.toByteArray(); + + Log.e("Sign_TX_Body", BTCUtils.toHex(rawData)); + + return rawData; + } + + int calculateSize(int inputCount, int outputCount) + { + int size = 0; + size += 4; // header + size += 1; //inputCount + + //hex str hash prev btc + + for(int i = 0; i < inputCount; ++i) + { + size += 32; //prevtx + size += 4; //outputIndex; + size += 1; //scriptLength + // size+=script; + size += 4; //ffffffff + } + + size+=1; //outputCount + size+=8; //amount + size+=1; + // size+=script; + + if(outputCount > 1) + { + size+=8; + size+=1; + //scriptLen; + } + + size+=4; + return size; + } + + + public static byte[] buildBodyTX(String outputAddress, String changeAddress, int outputIndex, String prevID, long amount, long change, byte[] script) throws BitcoinException, IOException { + + //0200000000 + BitcoinOutputStream forSign = new BitcoinOutputStream(); + forSign.writeInt32(0x01);//write(new byte[]{0x02, 0x00, 0x00, 0x00}); // version + + //01 + byte inputCount = 1; + forSign.write(inputCount); // input count + //hex str hash prev btc + byte[] txHash = BTCUtils.reverse(Util.hexToBytes(prevID));//Sha256Hash.hash(rawTxByte); + forSign.write(txHash); //previos tx hash + + //00000000 + //byte indexOutput = outputIndex; + forSign.writeInt32(outputIndex/*indexOutput*/); //output index in prev tx + //forSign.write(0x00); + + // hex str 1976a914....88ac + forSign.write((byte)script.length); + forSign.write(script); + + //ffffffff + forSign.write(new byte[]{(byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff}); // sequence + + //02 + byte outputCount = (byte)((change==0) ? 1 : 2); // outputCount + forSign.write(outputCount); + + //8 bytes + + forSign.writeInt64(amount); //amount + byte[] sendScript = Transaction.Script.buildOutput(outputAddress).bytes; // build out + //hex str 1976a914....88ac + forSign.write((byte)sendScript.length); + forSign.write(sendScript); + + if(change!=0){ + //8 bytes + forSign.writeInt64(change); // change + //hex str 1976a914....88ac + byte[] chancheScript = Transaction.Script.buildOutput(changeAddress).bytes; //build out + forSign.write((byte)chancheScript.length); + forSign.write(chancheScript); + + } + + //00000000 + forSign.write(new byte[]{0x00, 0x00, 0x00, 0x00}); + + byte[] rawData = forSign.toByteArray(); + + //Log.e("Sign_TX_Body", BTCUtils.toHex(rawData)); + return rawData; + } + + public static ArrayList getPrevTX(String hex) throws BitcoinException { + byte[] rawTxByte = fromHex(hex); + Transaction baseTx = new Transaction(rawTxByte); + ArrayList prevHashes = new ArrayList(); + for(int i =0; i < baseTx.inputs.length; ++i) + { + Transaction.Input input = baseTx.inputs[i]; + prevHashes.add(input.outPoint.hash); + } + return prevHashes; + } + + public static boolean isInput(String myAddress, String hex) throws BitcoinException { + byte[] rawTxByte = fromHex(hex); + Transaction baseTx = new Transaction(rawTxByte); + byte[] myScript = Transaction.Script.buildOutput(myAddress).bytes; + for(int i =0; i < baseTx.inputs.length; ++i) + { + Transaction.Input input = baseTx.inputs[i]; + byte[] script = input.script.bytes; + + // find outputs + if (Arrays.equals(myScript, script)){ + return true; + } + } + return false; + } + public static ArrayList getOutputs(List rawTxList, byte[] outputScriptWeAreAbleToSpend) throws BitcoinException { + ArrayList unspentOutputs = new ArrayList<>(); + + for(Tangem_Card.UnspentTransaction current: rawTxList) + { + byte[] rawTxByte = BTCUtils.fromHex(current.Raw); + if (rawTxByte == null) + { + continue; + } + + Transaction baseTx = new Transaction(rawTxByte); + + if(baseTx.inputs.length == 0 || baseTx.outputs.length == 0) + throw new IllegalArgumentException("Unable to decode given transaction"); + + byte[] txHash = BTCUtils.reverse(CryptoUtil.doubleSha256(rawTxByte)); + String txHashForBuild = current.txID; + byte[] sign = null; + + for (int outputIndex = 0; outputIndex < baseTx.outputs.length; outputIndex++) { + Transaction.Output output = baseTx.outputs[outputIndex]; + + // find outputs + if (Arrays.equals(outputScriptWeAreAbleToSpend, output.script.bytes)) { + unspentOutputs.add(new UnspentOutputInfo(txHash, output.script, output.value, outputIndex, -1, txHashForBuild, sign)); + } + } + + } + + return unspentOutputs; + } + + public static byte[] fromHex(String s) { + if (s != null) { + try { + StringBuilder sb = new StringBuilder(s.length()); + for (int i = 0; i < s.length(); i++) { + char ch = s.charAt(i); + if (!Character.isWhitespace(ch)) { + sb.append(ch); + } + } + s = sb.toString(); + int len = s.length(); + byte[] data = new byte[len / 2]; + for (int i = 0; i < len; i += 2) { + int hi = (Character.digit(s.charAt(i), 16) << 4); + int low = Character.digit(s.charAt(i + 1), 16); + if (hi >= 256 || low < 0 || low >= 16) { + return null; + } + data[i / 2] = (byte) (hi | low); + } + return data; + } catch (Exception ignored) { + } + } + return null; + } + + public static byte[] reverse(byte[] bytes) { + byte[] result = new byte[bytes.length]; + for (int i = 0; i < bytes.length; i++) { + result[i] = bytes[bytes.length - i - 1]; + } + return result; + } + + public static byte[] reverseInPlace(byte[] bytes) { + int len = bytes.length / 2; + for (int i = 0; i < len; i++) { + byte t = bytes[i]; + bytes[i] = bytes[bytes.length - i - 1]; + bytes[bytes.length - i - 1] = t; + } + return bytes; + } + + public static int findSpendableOutput(Transaction tx, String forAddress, long minAmount) throws BitcoinException { + byte[] outputScriptWeAreAbleToSpend = Transaction.Script.buildOutput(forAddress).bytes; + int indexOfOutputToSpend = -1; + for (int indexOfOutput = 0; indexOfOutput < tx.outputs.length; indexOfOutput++) { + Transaction.Output output = tx.outputs[indexOfOutput]; + if (Arrays.equals(outputScriptWeAreAbleToSpend, output.script.bytes)) { + indexOfOutputToSpend = indexOfOutput; + break;//only one input is supported for now + } + } + if (indexOfOutputToSpend == -1) { + throw new BitcoinException(BitcoinException.ERR_NO_SPENDABLE_OUTPUTS_FOR_THE_ADDRESS, "No spendable standard outputs for " + forAddress + " have found", forAddress); + } + final long spendableOutputValue = tx.outputs[indexOfOutputToSpend].value; + if (spendableOutputValue < minAmount) { + throw new BitcoinException(BitcoinException.ERR_INSUFFICIENT_FUNDS, "Unspent amount is too small: " + spendableOutputValue, spendableOutputValue); + } + return indexOfOutputToSpend; + } + + public static void verify(Transaction.Script[] scripts, Transaction spendTx) throws Transaction.Script.ScriptInvalidException { + for (int i = 0; i < scripts.length; i++) { + Stack stack = new Stack<>(); + spendTx.inputs[i].script.run(stack);//load signature+public key + scripts[i].run(i, spendTx, stack); //verify that this transaction able to spend that output + if (Transaction.Script.verifyFails(stack)) { + throw new Transaction.Script.ScriptInvalidException("Signature is invalid"); + } + } + } + + public static class FeeChangeAndSelectedOutputs { + public final long amountForRecipient, change, fee; + public final ArrayList outputsToSpend; + + public FeeChangeAndSelectedOutputs(long fee, long change, long amountForRecipient, ArrayList outputsToSpend) { + this.fee = fee; + this.change = change; + this.amountForRecipient = amountForRecipient; + this.outputsToSpend = outputsToSpend; + } + } + + public static FeeChangeAndSelectedOutputs calcFeeChangeAndSelectOutputsToSpend(List unspentOutputs, long amountToSend, long extraFee, final boolean isPublicKeyCompressed) throws BitcoinException { + long fee = 0;//calculated below + long change = 0; + long valueOfUnspentOutputs; + ArrayList outputsToSpend = new ArrayList<>(); + if (amountToSend <= 0) { + //transfer all funds from these addresses to outputAddress + change = 0; + valueOfUnspentOutputs = 0; + for (UnspentOutputInfo outputInfo : unspentOutputs) { + outputsToSpend.add(outputInfo); + valueOfUnspentOutputs += outputInfo.value; + } + final int txLen = BTCUtils.getMaximumTxSize(unspentOutputs, 1, isPublicKeyCompressed); + fee = BTCUtils.calcMinimumFee(txLen, unspentOutputs, valueOfUnspentOutputs - MIN_FEE_PER_KB * (1 + txLen / 1000)); + amountToSend = valueOfUnspentOutputs - fee - extraFee; + } else { + valueOfUnspentOutputs = 0; + for (UnspentOutputInfo outputInfo : unspentOutputs) { + outputsToSpend.add(outputInfo); + valueOfUnspentOutputs += outputInfo.value; + long updatedFee = MIN_FEE_PER_KB; + for (int i = 0; i < 3; i++) { + fee = updatedFee; + change = valueOfUnspentOutputs - fee - extraFee - amountToSend; + final int txLen = BTCUtils.getMaximumTxSize(unspentOutputs, change > 0 ? 2 : 1, isPublicKeyCompressed); + updatedFee = BTCUtils.calcMinimumFee(txLen, unspentOutputs, change > 0 ? Math.min(amountToSend, change) : amountToSend); + if (updatedFee == fee) { + break; + } + } + fee = updatedFee; + if (valueOfUnspentOutputs >= amountToSend + fee + extraFee) { + break; + } + } + + } + if (amountToSend > valueOfUnspentOutputs - fee) { + throw new BitcoinException(BitcoinException.ERR_INSUFFICIENT_FUNDS, "Not enough funds", valueOfUnspentOutputs - fee); + } + if (outputsToSpend.isEmpty()) { + throw new BitcoinException(BitcoinException.ERR_NO_INPUT, "No outputs to spend"); + } + if (fee + extraFee > MAX_ALLOWED_FEE) { + throw new BitcoinException(BitcoinException.ERR_FEE_IS_TOO_BIG, "Fee is too big", fee); + } + if (fee < 0 || extraFee < 0) { + throw new BitcoinException(BitcoinException.ERR_FEE_IS_LESS_THEN_ZERO, "Incorrect fee", fee); + } + if (change < 0) { + throw new BitcoinException(BitcoinException.ERR_CHANGE_IS_LESS_THEN_ZERO, "Incorrect change", change); + } + if (amountToSend < 0) { + throw new BitcoinException(BitcoinException.ERR_AMOUNT_TO_SEND_IS_LESS_THEN_ZERO, "Incorrect amount to send", amountToSend); + } + return new FeeChangeAndSelectedOutputs(fee + extraFee, change, amountToSend, outputsToSpend); + + } +} diff --git a/app/src/main/java/com/tangem/wallet/Base58.java b/app/src/main/java/com/tangem/wallet/Base58.java new file mode 100644 index 0000000000..0948e420fc --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/Base58.java @@ -0,0 +1,115 @@ +package com.tangem.wallet; + +import java.math.BigInteger; + +/** + * Created by Ilia on 15.02.2018. + */ + +public class Base58 { + private static final char[] BASE58 = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz".toCharArray(); + + private static final int BASE58_CHUNK_DIGITS = 10;//how many base 58 digits fits in long + private static final BigInteger BASE58_CHUNK_MOD = BigInteger.valueOf(0x5fa8624c7fba400L); //58^BASE58_CHUNK_DIGITS + private static final byte[] BASE58_VALUES = new byte[]{-1, -1, -1, -1, -1, -1, -1, -1, -1, -2, -2, -2, -2, -2, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, -1, -1, -1, -1, -1, -1, + -1, 9, 10, 11, 12, 13, 14, 15, 16, -1, 17, 18, 19, 20, 21, -1, + 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, -1, -1, -1, -1, -1, + -1, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, -1, 44, 45, 46, + 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}; + + public static byte[] decodeBase58(String input) { + if (input == null) { + return null; + } + input = input.trim(); + if (input.length() == 0) { + return new byte[0]; + } + BigInteger resultNum = BigInteger.ZERO; + int nLeadingZeros = 0; + while (nLeadingZeros < input.length() && input.charAt(nLeadingZeros) == BASE58[0]) { + nLeadingZeros++; + } + long acc = 0; + int nDigits = 0; + int p = nLeadingZeros; + while (p < input.length()) { + int v = BASE58_VALUES[input.charAt(p) & 0xff]; + if (v >= 0) { + acc *= 58; + acc += v; + nDigits++; + if (nDigits == BASE58_CHUNK_DIGITS) { + resultNum = resultNum.multiply(BASE58_CHUNK_MOD).add(BigInteger.valueOf(acc)); + acc = 0; + nDigits = 0; + } + p++; + } else { + break; + } + } + if (nDigits > 0) { + long mul = 58; + while (--nDigits > 0) { + mul *= 58; + } + resultNum = resultNum.multiply(BigInteger.valueOf(mul)).add(BigInteger.valueOf(acc)); + } + final int BASE58_SPACE = -2; + while (p < input.length() && BASE58_VALUES[input.charAt(p) & 0xff] == BASE58_SPACE) { + p++; + } + if (p < input.length()) { + return null; + } + byte[] plainNumber = resultNum.toByteArray(); + int plainNumbersOffs = plainNumber[0] == 0 ? 1 : 0; + byte[] result = new byte[nLeadingZeros + plainNumber.length - plainNumbersOffs]; + System.arraycopy(plainNumber, plainNumbersOffs, result, nLeadingZeros, plainNumber.length - plainNumbersOffs); + return result; + } + + public static String encodeBase58(byte[] input) { + if (input == null) { + return null; + } + StringBuilder str = new StringBuilder((input.length * 350) / 256 + 1); + BigInteger bn = new BigInteger(1, input); + long rem; + while (true) { + BigInteger[] divideAndRemainder = bn.divideAndRemainder(BASE58_CHUNK_MOD); + bn = divideAndRemainder[0]; + rem = divideAndRemainder[1].longValue(); + if (bn.compareTo(BigInteger.ZERO) == 0) { + break; + } + for (int i = 0; i < BASE58_CHUNK_DIGITS; i++) { + str.append(BASE58[(int) (rem % 58)]); + rem /= 58; + } + } + while (rem != 0) { + str.append(BASE58[(int) (rem % 58)]); + rem /= 58; + } + str.reverse(); + int nLeadingZeros = 0; + while (nLeadingZeros < input.length && input[nLeadingZeros] == 0) { + str.insert(0, BASE58[0]); + nLeadingZeros++; + } + return str.toString(); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/wallet/BitcoinException.java b/app/src/main/java/com/tangem/wallet/BitcoinException.java new file mode 100644 index 0000000000..4051775e60 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/BitcoinException.java @@ -0,0 +1,35 @@ +package com.tangem.wallet; + +/** + * Created by Ilia on 29.09.2017. + */ + +@SuppressWarnings("WeakerAccess") +public final class BitcoinException extends Exception { + public static final int ERR_NO_SPENDABLE_OUTPUTS_FOR_THE_ADDRESS = 0; + public static final int ERR_INSUFFICIENT_FUNDS = 1; + public static final int ERR_WRONG_TYPE = 2; + public static final int ERR_BAD_FORMAT = 3; + public static final int ERR_INCORRECT_PASSWORD = 4; + public static final int ERR_MEANINGLESS_OPERATION = 5; + public static final int ERR_NO_INPUT = 6; + public static final int ERR_FEE_IS_TOO_BIG = 7; + public static final int ERR_FEE_IS_LESS_THEN_ZERO = 8; + public static final int ERR_CHANGE_IS_LESS_THEN_ZERO = 9; + public static final int ERR_AMOUNT_TO_SEND_IS_LESS_THEN_ZERO = 10; + public static final int ERR_UNSUPPORTED = 11; + + public final int errorCode; + @SuppressWarnings({"WeakerAccess", "unused"}) + public final Object extraInformation; + + public BitcoinException(int errorCode, String detailMessage, Object extraInformation) { + super(detailMessage); + this.errorCode = errorCode; + this.extraInformation = extraInformation; + } + + public BitcoinException(int errorCode, String detailMessage) { + this(errorCode, detailMessage, null); + } +} diff --git a/app/src/main/java/com/tangem/wallet/BitcoinInputStream.java b/app/src/main/java/com/tangem/wallet/BitcoinInputStream.java new file mode 100644 index 0000000000..fa0ed57c0d --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/BitcoinInputStream.java @@ -0,0 +1,69 @@ +package com.tangem.wallet; + +/** + * Created by Ilia on 29.09.2017. + */ + +import java.io.ByteArrayInputStream; +import java.io.EOFException; +import java.io.IOException; + +@SuppressWarnings("WeakerAccess") +public class BitcoinInputStream extends ByteArrayInputStream { + public BitcoinInputStream(byte[] buf) { + super(buf); + } + + @SuppressWarnings("unused") + public BitcoinInputStream(byte[] buf, int offset, int length) { + super(buf, offset, length); + } + + public int readInt16() throws EOFException { + return (readByte() & 0xff) | ((readByte() & 0xff) << 8); + } + + public int readInt32() throws EOFException { + return (readByte() & 0xff) | ((readByte() & 0xff) << 8) | ((readByte() & 0xff) << 16) | ((readByte() & 0xff) << 24); + } + + public long readInt64() throws EOFException { + return (readInt32() & 0xFFFFFFFFL )| ((readInt32() & 0xFFFFFFFFL) << 32); + } + + public int readByte() throws EOFException { + int readedByte = super.read(); + if (readedByte == -1) { + throw new EOFException(); + } + return readedByte; + } + + public long readVarInt() throws EOFException { + int readedByte = readByte(); + if (readedByte < 0xfd) { + return readedByte; + } else if (readedByte == 0xfd) { + return readInt16(); + } else if (readedByte == 0xfe) { + return readInt32(); + } else { + return readInt64(); + } + } + + public byte[] readChars(final int count) throws IOException { + byte[] buf = new byte[count]; + int off = 0; + while (off != count) { + int bytesReadCurr = read(buf, off, count - off); + if (bytesReadCurr == -1) { + throw new EOFException(); + } else { + off += bytesReadCurr; + } + } + return buf; + } + +} diff --git a/app/src/main/java/com/tangem/wallet/BitcoinOutputStream.java b/app/src/main/java/com/tangem/wallet/BitcoinOutputStream.java new file mode 100644 index 0000000000..7963eee0d5 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/BitcoinOutputStream.java @@ -0,0 +1,42 @@ +package com.tangem.wallet; + +/** + * Created by Ilia on 29.09.2017. + */ +import java.io.ByteArrayOutputStream; + +@SuppressWarnings("WeakerAccess") +public final class BitcoinOutputStream extends ByteArrayOutputStream { + + public void writeInt16(int value) { + write(value & 0xff); + write((value >> 8) & 0xff); + } + + public void writeInt32(int value) { + write(value & 0xff); + write((value >> 8) & 0xff); + write((value >> 16) & 0xff); + write((value >>> 24) & 0xff); + } + + public void writeInt64(long value) { + writeInt32((int) (value & 0xFFFFFFFFL)); + writeInt32((int) ((value >>> 32) & 0xFFFFFFFFL)); + } + + public void writeVarInt(long value) { + if (value < 0xfd) { + write((int) (value & 0xff)); + } else if (value < 0xffff) { + write(0xfd); + writeInt16((int) value); + } else if (value < 0xffffffffL) { + write(0xfe); + writeInt32((int) value); + } else { + write(0xff); + writeInt64(value); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/wallet/Blockchain.java b/app/src/main/java/com/tangem/wallet/Blockchain.java new file mode 100644 index 0000000000..bf214a32a8 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/Blockchain.java @@ -0,0 +1,99 @@ +package com.tangem.wallet; + +import android.net.Uri; + +import com.google.common.base.Strings; +import com.tangem.cardReader.CardProtocol; +import com.tangem.cardReader.Util; + +import org.bitcoinj.core.Base58; + +import java.nio.ByteBuffer; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +/** + * Created by dvol on 06.08.2017. + */ +public enum Blockchain { + Unknown("", "", 1.0, R.drawable.ic_logo_small, ""), + Bitcoin("BTC", "BTC", 100000000.0, R.drawable.bitcoins, "Bitcoin"), + BitcoinTestNet("BTC/test", "BTC", 100000000.0, R.drawable.bitcoins_testnet, "Bitcoin Testnet"), + Ethereum("ETH", "ETH", 1.0, R.drawable.ethereum, "Ethereum"), + EthereumTestNet("ETH/test", "ETH", 1.0, R.drawable.ethereum_testnet, "Ethereum Testnet"), + Token("ETH\\XTZ", "BAT", 1.0, R.drawable.bat_token, "Ethereum"), + BitcoinCash("BCH", "BCH", 100000000.0, R.drawable.bitcoin_cash, "Bitcoin Cash"), + BitcoinCashTestNet("BCH/test", "BTC", 100000000.0, R.drawable.bitcoin_cash, "Bitcoin Cash Testnet"); + + + Blockchain(String ID, String Currency, double Multiplier, int ImageResource, String officialName) { + mID = ID; + mCurrency = Currency; + mMultiplier = Multiplier; + mImageResource = ImageResource; + mOfficialName = officialName; + } + + private String mID, mOfficialName; + private double mMultiplier; + private String mCurrency; + private int mImageResource; + + public String getID() { + return mID; + } + + public String getOfficialName() { + return mOfficialName; + } + + public double getMultiplier() { + return mMultiplier; + } + + public String getCurrency() { + return mCurrency; + } + + public static Blockchain fromId(String id) { + for (Blockchain blockchain : values()) { + if (blockchain.getID().equals(id)) return blockchain; + } + return null; + } + + public static Blockchain fromCurrency(String currency) { + for (Blockchain blockchain : values()) { + if (blockchain.getCurrency() == currency) return blockchain; + } + return null; + } + + public static String[] getCurrencies() { + String[] result = new String[values().length - 1]; + for (int i = 0; i < result.length - 1; i++) { + result[i] = values()[i + 1].getCurrency(); + } + return result; + } + + public int getImageResource() { + return mImageResource; + } + + public int getImageResource(android.content.Context context, String name) { + if(Strings.isNullOrEmpty(name)) + return getImageResource(); + + name = name.toLowerCase(); + + int resourceId = context.getResources().getIdentifier(name+"_token", "drawable", context.getPackageName()); + + if(resourceId <= 0) + return R.drawable.ethereum; + return resourceId; + } +} diff --git a/app/src/main/java/com/tangem/wallet/BtcCashEngine.java b/app/src/main/java/com/tangem/wallet/BtcCashEngine.java new file mode 100644 index 0000000000..076d7b2905 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/BtcCashEngine.java @@ -0,0 +1,428 @@ +package com.tangem.wallet; + +import android.net.Uri; + +import com.tangem.cardReader.CardProtocol; +import com.tangem.cardReader.TLV; +import com.tangem.cardReader.Util; + +import java.io.ByteArrayOutputStream; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.nio.ByteBuffer; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.text.DecimalFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.List; + +import static com.tangem.wallet.FormatUtil.GetDecimalFormat; + +/** + * Created by Ilia on 15.02.2018. + */ + +public class BtcCashEngine extends CoinEngine{ + public String GetNextNode(Tangem_Card mCard) + { + + return "35.157.238.5"; + } + public int GetNextNodePort(Tangem_Card mCard) + { + + return 51001; + } + public String GetNode(Tangem_Card mCard) + { + return "35.157.238.5"; + } + public int GetNodePort(Tangem_Card mCard) + { + return 51001; + } + + public void SwitchNode(Tangem_Card mCard) + { + } + + public boolean InOutPutVisible() + { + return true; + } + + public boolean AwaitingConfirmation(Tangem_Card card) + { + return card.getBalanceUnconfirmed()!=0; + } + + public String GetBalanceWithAlter(Tangem_Card mCard) + { + return GetBalance(mCard); + } + + public boolean IsBalanceAlterNotZero(Tangem_Card card) + { + return true; + } + + public Long GetBalanceLong(Tangem_Card mCard) + { + return mCard.getBalance(); + } + + public boolean IsBalanceNotZero(Tangem_Card card) + { + return card.getBalance() > 0; + } + + public boolean CheckAmount(Tangem_Card card, String amount) throws Exception + { + DecimalFormat decimalFormat = GetDecimalFormat(); + BigDecimal amountValue = (BigDecimal) decimalFormat.parse(amount); + + // Convert Balance to BigDecimal + BigDecimal maxValue = new BigDecimal(GetBalanceValue(card)); + maxValue = maxValue.divide(new BigDecimal(1000)); + + //if (use_mCurrency) { + amountValue = amountValue.divide(new BigDecimal(1000)); + //} + + if (amountValue.compareTo(maxValue) > 0) { + return false; + } + + return true; + } + + public boolean HasBalanceInfo(Tangem_Card card) + { + return card.hasBalanceInfo(); + } + + public String GetBalanceCurrency(Tangem_Card card) + { + return "mBCH"; + } + + public boolean CheckUnspentTransaction(Tangem_Card mCard) + { + return mCard.getUnspentTransactions().size() != 0; + } + + public String GetFeeCurrency() + { + return "mBCH"; + } + + public boolean ValdateAddress(String address, Tangem_Card card){ + if(address == null || address.isEmpty()) + { + return false; + } + + if(address.length() < 25) + { + return false; + } + + if(address.length() > 35) + { + return false; + } + + if(!address.startsWith("1") && !address.startsWith("2") && !address.startsWith("3") && !address.startsWith("n") && !address.startsWith("m")) + { + return false; + } + + byte[] decAddress = Base58.decodeBase58(address); + + if(decAddress == null || decAddress.length == 0) + { + return false; + } + + byte[] rip = new byte[21]; + for(int i =0; i < 21; ++i) + { + rip[i] = decAddress[i]; + } + + byte[] kcv = CryptoUtil.doubleSha256(rip); + + for(int i =0; i < 4; ++i) + { + if(kcv[i] != decAddress[21+i]) + return false; + } + + if(card.getBlockchain()!=Blockchain.BitcoinCashTestNet && card.getBlockchain()!=Blockchain.BitcoinCash) + { + return false; + } + + if(card.getBlockchain()==Blockchain.BitcoinCashTestNet && (address.startsWith("1") || address.startsWith("3"))) + { + return false; + } + + return true; + } + + + public int GetTokenDecimals(Tangem_Card card) + { + return 0; + } + + public String GetContractAddress(Tangem_Card card) + { + return ""; + } + + public boolean IsNeedCheckNode() + { + return true; + } + + public Uri getShareWalletURIExplorer(Tangem_Card mCard) + { + return Uri.parse((mCard.getBlockchain() == Blockchain.BitcoinCash ? "https://bitcoincash.blockexplorer.com/address/" : "https://testnet.blockexplorer.com/address/") + mCard.getWallet()); + } + public Uri getShareWalletURI(Tangem_Card mCard) + { + return Uri.parse("bitcoincash:" + mCard.getWallet()); + } + public boolean CheckAmountValie(Tangem_Card mCard, String amountValue, String feeValue, Long minFeeInInternalUnits) + { + Long fee = null; + Long amount = null; + try { + amount = mCard.InternalUnitsFromString(amountValue); + fee = mCard.InternalUnitsFromString(feeValue); + } catch (Exception e) { + e.printStackTrace(); + return false; + } + + if(fee == null || amount == null) + return false; + + if(fee == 0 || amount ==0) + return false; + + if(fee > amount) + return false; + + if(fee < minFeeInInternalUnits) + return false; + + return true; + } + + public String EvaluteFeeEquivalent(Tangem_Card mCard, String fee) + { + return GetAmountEqualentDescriptor(mCard, fee); + } + + @Override + public String GetBalanceEquivalent(Tangem_Card mCard) { + Double balance = Double.NaN; + try{ + Long val = mCard.getBalance(); + balance = mCard.AmountFromInternalUnits(val); + } + catch(Exception ex) + { + mCard.setRate(0); + } + + return mCard.getAmountEquivalentDescription(balance); + } + + public String GetBalance(Tangem_Card mCard) + { + if (mCard.hasBalanceInfo()) { + Double balance = mCard.AmountFromInternalUnits(mCard.getBalance()); + return mCard.getAmountDescription(balance); + } else { + return "-- -- -- " + mCard.getBlockchain().getCurrency(); + } + } + + public String GetBalanceValue(Tangem_Card mCard) + { + if (mCard.hasBalanceInfo()) { + Double balance = mCard.getBalance() / (mCard.getBlockchain().getMultiplier() / 1000.0); + + String output = FormatUtil.DoubleToString(balance); + //String pattern = "#0.000"; // If you like 4 zeros + //DecimalFormat myFormatter = new DecimalFormat(pattern); + //String output = myFormatter.format(balance); + return output; + + //return Double.toString(balance); + } + else + { + return "0"; + } + } + + public String calculateAddress(Tangem_Card mCard, byte[] pkUncompressed) throws NoSuchProviderException, NoSuchAlgorithmException { + + byte netSelectionByte; + switch (mCard.getBlockchain()) { + case BitcoinCash: + netSelectionByte = (byte) 0x00; //0 - MainNet 0x6f - TestNet + break; + default : + netSelectionByte = (byte) 0x6f; //0 - MainNet 0x6f - TestNet + break; + } + + byte hash1[] = Util.calculateSHA256(pkUncompressed); + byte hash2[] = Util.calculateRIPEMD160(hash1); + + ByteBuffer BB = ByteBuffer.allocate(hash2.length + 1); + + BB.put(netSelectionByte); + BB.put(hash2); + + byte hash3[] = Util.calculateSHA256(BB.array()); + byte hash4[] = Util.calculateSHA256(hash3); + + BB = ByteBuffer.allocate(hash2.length + 5); + BB.put(netSelectionByte); //BB.put((byte) 0x6f); + BB.put(hash2); + BB.put(hash4[0]); + BB.put(hash4[1]); + BB.put(hash4[2]); + BB.put(hash4[3]); + + return org.bitcoinj.core.Base58.encode(BB.array()); + + } + + @Override + public String ConvertByteArrayToAmount(Tangem_Card mCard, byte[] bytes) throws Exception { + byte[] reversed=new byte[bytes.length]; + for(int i=0; i 0) { + return String.format("≈ USD %.2f", amount * rate); + } else { + return "≈ USD  ---"; + } + } + + public String GetAmountEqualentDescriptor(Tangem_Card mCard, String value) + { + return getAmountEquivalentDescriptionBTC(Double.parseDouble(value)/1000.0, mCard.getRate()); + } + + public byte[] Sign(String feeValue, String amountValue, String toValue, Tangem_Card mCard, CardProtocol protocol) throws Exception { + + String myAddress = mCard.getWallet(); + byte[] pbKey = mCard.getWalletPublicKeyRar(); //ALWAYS USING COMPRESS KEY + String outputAddress = toValue; + String changeAddress = myAddress; + + // Build script for our address + List rawTxList = mCard.getUnspentTransactions(); + byte[] outputScriptWeAreAbleToSpend = Transaction.Script.buildOutput(myAddress).bytes; + + // Collect unspent + ArrayList unspentOutputs = BTCUtils.getOutputs(rawTxList, outputScriptWeAreAbleToSpend); + + long fullAmount = 0; + for (int i = 0; i < unspentOutputs.size(); ++i) { + fullAmount += unspentOutputs.get(i).value; + } + + + long fees = FormatUtil.ConvertStringToLong(feeValue); + long amount = FormatUtil.ConvertStringToLong(amountValue); + amount = amount - fees; + + long change = fullAmount - fees - amount; + + if (amount + fees > fullAmount) { + throw new Exception(String.format("Balance (%d) < amount (%d) + (%d)", fullAmount, change, amount)); + } + + byte[][] dataForSign = new byte[unspentOutputs.size()][]; + + for (int i = 0; i < unspentOutputs.size(); ++i) { + byte[] newTX = BTCUtils.buildTXForSign(myAddress, outputAddress, changeAddress, unspentOutputs, i, amount, change); + + byte[] hashData = Util.calculateSHA256(newTX); + byte[] doubleHashData = Util.calculateSHA256(hashData); + + unspentOutputs.get(i).bodyDoubleHash = doubleHashData; + unspentOutputs.get(i).bodyHash = hashData; + + if(mCard.getSigningMethod() == Tangem_Card.SigningMethod.Sign_Raw || mCard.getSigningMethod() == Tangem_Card.SigningMethod.Sign_Raw_Validated_By_Issuer) + { + dataForSign[i] = newTX; + } + else + { + dataForSign[i] = doubleHashData; + } + + } + + byte[] signFromCard = null; + if(mCard.getSigningMethod() == Tangem_Card.SigningMethod.Sign_Raw || mCard.getSigningMethod() == Tangem_Card.SigningMethod.Sign_Raw_Validated_By_Issuer) + { + ByteArrayOutputStream bs = new ByteArrayOutputStream(); + if (dataForSign.length > 10) throw new Exception("To much hashes in one transaction!"); + for (int i = 0; i < dataForSign.length; i++) { + if (i != 0 && dataForSign[0].length != dataForSign[i].length) + throw new Exception("Hashes length must be identical!"); + bs.write(dataForSign[i]); + } + signFromCard = protocol.run_SignRaw(PINStorage.getPIN2(), bs.toByteArray()).getTLV(TLV.Tag.TAG_Signature).Value; + } + else { + signFromCard = protocol.run_SignHashes(PINStorage.getPIN2(), dataForSign, mCard.getSigningMethod() == Tangem_Card.SigningMethod.Sign_Hash_Validated_By_Issuer, null, mCard.getIssuer()).getTLV(TLV.Tag.TAG_Signature).Value; + // TODO slice signFromCard to hashes.length parts + } + + LastSignStorage.setLastSignDate(mCard.getWallet(), new Date()); + + + for (int i = 0; i < unspentOutputs.size(); ++i) { + BigInteger r = new BigInteger(1, Arrays.copyOfRange(signFromCard, 0 + i * 64, 32 + i * 64)); + BigInteger s = new BigInteger(1, Arrays.copyOfRange(signFromCard, 32 + i * 64, 64 + i * 64)); + s = CryptoUtil.toCanonicalised(s); + byte[] encodingSign = DerEncodingUtil.packSignDerBitcoinCash(r, s, pbKey); + + unspentOutputs.get(i).scriptForBuild = encodingSign; + } + + byte[] realTX = BTCUtils.buildTXForSend(outputAddress, changeAddress, unspentOutputs, amount, change); + return realTX; + } +} diff --git a/app/src/main/java/com/tangem/wallet/BtcEngine.java b/app/src/main/java/com/tangem/wallet/BtcEngine.java new file mode 100644 index 0000000000..963ff746d9 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/BtcEngine.java @@ -0,0 +1,564 @@ +package com.tangem.wallet; + +import android.net.Uri; + +import com.tangem.cardReader.CardProtocol; +import com.tangem.cardReader.TLV; +import com.tangem.cardReader.Util; + +import java.io.ByteArrayOutputStream; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.nio.ByteBuffer; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.text.DecimalFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.List; +import java.util.Random; + +import static com.tangem.wallet.FormatUtil.GetDecimalFormat; + +/** + * Created by Ilia on 15.02.2018. + */ + +public class BtcEngine extends CoinEngine{ + public String GetNextNode(Tangem_Card mCard) + { + return getNextServiceHost(mCard); + } + public int GetNextNodePort(Tangem_Card mCard) + { + return getNextServicePort(mCard); + } + public String GetNode(Tangem_Card mCard) + { + return getServiceHost(mCard); + } + public int GetNodePort(Tangem_Card mCard) + { + return getServicePort(mCard); + } + + public void SwitchNode(Tangem_Card mCard) + { + SelectNextBitconServiceIndex(); + } + + public static String[] GetBitcoinServiceHosts() { + return new String[]{"vps.hsmiths.com", "tardis.bauerj.eu" /*"arihancckjge66iv.onion"*/, "electrumx.bot.nu","electrumx.hopto.org"/* "btc.asis.io"*/, "e-x.not.fyi", "electrum.backplanedns.org", "helicarrier.bauerj.eu", "electrum.vom-stausee.de", "electrum0.snel.it", "kirsche.emzy.de"}; + } + + public static String[] GetBitcoinTestNetServiceHosts() { + return new String[]{/*"testnetnode.arihanc.com"*/"testnet.hsmiths.com", "testnet.qtornado.com", "testnet1.bauerj.eu"}; + } + + public static Integer[] GetBitcoinServicePorts() { + return new Integer[]{8080,50001/* 8080*/, 50001, 50001, 50001, 50001, 50001, 50001, 50001, 50001}; + } + + public static Integer[] GetBitcoinTestNetServicePorts() { + return new Integer[]{/*51001*/53011, 51001, 50001}; + } + + static int serviceIndex = GetNextBitconMainNetServiceIndex(); + + static int dynamicIndex = GetNextBitconMainNetServiceIndex(); + + static int serviceIndexTestNet = GetNextBitconTestNetServiceIndex(); + + static int dynamicTestNetIndex = GetNextBitconTestNetServiceIndex(); + + static long lastChangeServiceIndex = 0; + + public static int GetNextBitconMainNetServiceIndex() { + Random r = new Random(); + return r.nextInt(GetBitcoinServiceHosts().length); + } + + public static void SelectNextBitconMainNetServiceIndex() { + //serviceIndex = GetNextBitconMainNetServiceIndex(); + serviceIndex++; + if (serviceIndex > GetBitcoinServiceHosts().length - 1) serviceIndex = 0; + } + + public static int GetNextBitconTestNetServiceIndex() { + Random r = new Random(); + return r.nextInt(GetBitcoinTestNetServiceHosts().length); + } + + public static void SelectNextBitconTestNetServiceIndex() { + //serviceIndexTestNet = GetNextBitconTestNetServiceIndex(); + + serviceIndexTestNet++; + if (serviceIndexTestNet > GetBitcoinTestNetServiceHosts().length - 1) serviceIndexTestNet = 0; + } + + public static void setNextDynamicIndex() { + //dynamicIndex = GetNextBitconMainNetServiceIndex(); + dynamicIndex++; + if (dynamicIndex > GetBitcoinServiceHosts().length - 1) dynamicIndex = 0; + } + + public static void setNextDynamicTestNet() { + //dynamicTestNetIndex = GetNextBitconTestNetServiceIndex(); + dynamicTestNetIndex++; + if (dynamicTestNetIndex > GetBitcoinTestNetServiceHosts().length - 1) dynamicTestNetIndex = 0; + + } + + public static void SelectNextBitconServiceIndex() { + long unixTime = System.currentTimeMillis() / 1000L; + long nextStampOffset = 5; + if(lastChangeServiceIndex == 0) + { + lastChangeServiceIndex = unixTime; + } + else if(lastChangeServiceIndex + nextStampOffset > unixTime ) + { + return; + } + + lastChangeServiceIndex = unixTime; + + SelectNextBitconMainNetServiceIndex(); + SelectNextBitconTestNetServiceIndex(); + } + + public static String getServiceHost(Tangem_Card mCard) { + switch (mCard.getBlockchain()) { + case Bitcoin: + return GetBitcoinServiceHosts()[serviceIndex]; //"hsmiths.changeip.net"; + case BitcoinTestNet: + return GetBitcoinTestNetServiceHosts()[serviceIndexTestNet]; //"testnetnode.arihanc.com"; + } + return null; + } + + public static String getNextServiceHost(Tangem_Card mCard) { + switch (mCard.getBlockchain()) { + case Bitcoin: { + setNextDynamicIndex(); + return GetBitcoinServiceHosts()[dynamicIndex]; + } + case BitcoinTestNet: { + setNextDynamicTestNet(); + return GetBitcoinTestNetServiceHosts()[dynamicTestNetIndex]; //"testnetnode.arihanc.com"; + } + //case BitcoinCash: + //{ + // return GetBitcoinCashServiceHosts()[0]; + //} + //case BitcoinCashTestNet: { + // return GetBitcoinCashTestNetServiceHosts()[0]; + //} + } + return null; + } + + public static int getNextServicePort(Tangem_Card mCard) { + switch (mCard.getBlockchain()) { + case Bitcoin: { + setNextDynamicIndex(); + return GetBitcoinServicePorts()[dynamicIndex];//8080; + } + case BitcoinTestNet: { + setNextDynamicTestNet(); + return GetBitcoinTestNetServicePorts()[dynamicTestNetIndex];//51001; + } + } + return 8080; + } + + public static int getServicePort(Tangem_Card mCard) { + switch (mCard.getBlockchain()) { + case Bitcoin: + return GetBitcoinServicePorts()[serviceIndex];//8080; + case BitcoinTestNet: + return GetBitcoinTestNetServicePorts()[serviceIndexTestNet];//51001; + } + return 8080; + } + + + public boolean InOutPutVisible() + { + return true; + } + + public boolean AwaitingConfirmation(Tangem_Card card) + { + return card.getBalanceUnconfirmed()!=0; + } + + public String GetBalanceWithAlter(Tangem_Card mCard) + { + return GetBalance(mCard); + } + + public boolean IsBalanceAlterNotZero(Tangem_Card card) + { + return true; + } + + public Long GetBalanceLong(Tangem_Card mCard) + { + return mCard.getBalance(); + } + + public boolean IsBalanceNotZero(Tangem_Card card) + { + return card.getBalance() > 0; + } + + public boolean CheckAmount(Tangem_Card card, String amount) throws Exception + { + DecimalFormat decimalFormat = GetDecimalFormat(); + BigDecimal amountValue = (BigDecimal) decimalFormat.parse(amount); + + // Convert Balance to BigDecimal + BigDecimal maxValue = new BigDecimal(GetBalanceValue(card)); + maxValue = maxValue.divide(new BigDecimal(1000)); + + //if (use_mCurrency) { + amountValue = amountValue.divide(new BigDecimal(1000)); + //} + + if (amountValue.compareTo(maxValue) > 0) { + return false; + } + + return true; + } + + public boolean HasBalanceInfo(Tangem_Card card) + { + return card.hasBalanceInfo(); + } + + public String GetBalanceCurrency(Tangem_Card card) + { + return "mBTC"; + } + + public boolean CheckUnspentTransaction(Tangem_Card mCard) + { + return mCard.getUnspentTransactions().size() != 0; + } + + public String GetFeeCurrency() + { + return "mBTC"; + } + + public boolean ValdateAddress(String address, Tangem_Card card){ + if(address == null || address.isEmpty()) + { + return false; + } + + if(address.length() < 25) + { + return false; + } + + if(address.length() > 35) + { + return false; + } + + if(!address.startsWith("1") && !address.startsWith("2") && !address.startsWith("3") && !address.startsWith("n") && !address.startsWith("m")) + { + return false; + } + + byte[] decAddress = Base58.decodeBase58(address); + + if(decAddress == null || decAddress.length == 0) + { + return false; + } + + byte[] rip = new byte[21]; + for(int i =0; i < 21; ++i) + { + rip[i] = decAddress[i]; + } + + byte[] kcv = CryptoUtil.doubleSha256(rip); + + for(int i =0; i < 4; ++i) + { + if(kcv[i] != decAddress[21+i]) + return false; + } + + if(card.getBlockchain()!=Blockchain.BitcoinTestNet && card.getBlockchain()!=Blockchain.Bitcoin) + { + return false; + } + + if(card.getBlockchain()==Blockchain.BitcoinTestNet && (address.startsWith("1") || address.startsWith("3"))) + { + return false; + } + + return true; + } + + + public int GetTokenDecimals(Tangem_Card card) + { + return 0; + } + + public String GetContractAddress(Tangem_Card card) + { + return ""; + } + + public boolean IsNeedCheckNode() + { + return true; + } + + public Uri getShareWalletURIExplorer(Tangem_Card mCard) + { + return Uri.parse((mCard.getBlockchain() == Blockchain.Bitcoin ? "https://blockchain.info/address/" : "https://testnet.blockchain.info/address/") + mCard.getWallet()); + } + public Uri getShareWalletURI(Tangem_Card mCard) + { + return Uri.parse("bitcoin:" + mCard.getWallet()); + } + public boolean CheckAmountValie(Tangem_Card mCard, String amountValue, String feeValue, Long minFeeInInternalUnits) + { + Long fee = null; + Long amount = null; + try { + amount = mCard.InternalUnitsFromString(amountValue); + fee = mCard.InternalUnitsFromString(feeValue); + } catch (Exception e) { + e.printStackTrace(); + return false; + } + + if(fee == null || amount == null) + return false; + + if(fee == 0 || amount ==0) + return false; + + if(fee > amount) + return false; + + if(fee < minFeeInInternalUnits) + return false; + + return true; + } + + public String EvaluteFeeEquivalent(Tangem_Card mCard, String fee) + { + return GetAmountEqualentDescriptor(mCard, fee); + } + + @Override + public String GetBalanceEquivalent(Tangem_Card mCard) { + Double balance = Double.NaN; + try{ + Long val = mCard.getBalance(); + balance = mCard.AmountFromInternalUnits(val); + } + catch(Exception ex) + { + mCard.setRate(0); + } + + return mCard.getAmountEquivalentDescription(balance); + } + + public String GetBalance(Tangem_Card mCard) + { + if (mCard.hasBalanceInfo()) { + Double balance = mCard.AmountFromInternalUnits(mCard.getBalance()); + return mCard.getAmountDescription(balance); + } else { + return "-- -- -- " + mCard.getBlockchain().getCurrency(); + } + } + + public String GetBalanceValue(Tangem_Card mCard) + { + if (mCard.hasBalanceInfo()) { + Double balance = mCard.getBalance() / (mCard.getBlockchain().getMultiplier() / 1000.0); + + String output = FormatUtil.DoubleToString(balance); + //String pattern = "#0.000"; // If you like 4 zeros + //DecimalFormat myFormatter = new DecimalFormat(pattern); + //String output = myFormatter.format(balance); + return output; + + //return Double.toString(balance); + } + else + { + return "0"; + } + } + + public String calculateAddress(Tangem_Card mCard, byte[] pkUncompressed) throws NoSuchProviderException, NoSuchAlgorithmException { + + byte netSelectionByte; + switch (mCard.getBlockchain()) { + case Bitcoin: + netSelectionByte = (byte) 0x00; //0 - MainNet 0x6f - TestNet + break; + default : + netSelectionByte = (byte) 0x6f; //0 - MainNet 0x6f - TestNet + break; + } + + byte hash1[] = Util.calculateSHA256(pkUncompressed); + byte hash2[] = Util.calculateRIPEMD160(hash1); + + ByteBuffer BB = ByteBuffer.allocate(hash2.length + 1); + + BB.put(netSelectionByte); + BB.put(hash2); + + byte hash3[] = Util.calculateSHA256(BB.array()); + byte hash4[] = Util.calculateSHA256(hash3); + + BB = ByteBuffer.allocate(hash2.length + 5); + BB.put(netSelectionByte); //BB.put((byte) 0x6f); + BB.put(hash2); + BB.put(hash4[0]); + BB.put(hash4[1]); + BB.put(hash4[2]); + BB.put(hash4[3]); + + return org.bitcoinj.core.Base58.encode(BB.array()); + + } + + @Override + public String ConvertByteArrayToAmount(Tangem_Card mCard, byte[] bytes) throws Exception { + byte[] reversed=new byte[bytes.length]; + for(int i=0; i 0) { + return String.format("≈ USD %.2f", amount * rate); + } else { + return "≈ USD  ---"; + } + } + + public String GetAmountEqualentDescriptor(Tangem_Card mCard, String value) + { + return getAmountEquivalentDescriptionBTC(Double.parseDouble(value)/1000.0, mCard.getRate()); + } + + public byte[] Sign(String feeValue, String amountValue, String toValue, Tangem_Card mCard, CardProtocol protocol) throws Exception { + + String myAddress = mCard.getWallet(); + byte[] pbKey = mCard.getWalletPublicKey(); + String outputAddress = toValue; + String changeAddress = myAddress; + + // Build script for our address + List rawTxList = mCard.getUnspentTransactions(); + byte[] outputScriptWeAreAbleToSpend = Transaction.Script.buildOutput(myAddress).bytes; + + // Collect unspent + ArrayList unspentOutputs = BTCUtils.getOutputs(rawTxList, outputScriptWeAreAbleToSpend); + + long fullAmount = 0; + for (int i = 0; i < unspentOutputs.size(); ++i) { + fullAmount += unspentOutputs.get(i).value; + } + + + long fees = FormatUtil.ConvertStringToLong(feeValue); + long amount = FormatUtil.ConvertStringToLong(amountValue); + amount = amount - fees; + + long change = fullAmount - fees - amount; + + if (amount + fees > fullAmount) { + throw new Exception(String.format("Balance (%d) < amount (%d) + (%d)", fullAmount, change, amount)); + } + + byte[][] dataForSign = new byte[unspentOutputs.size()][]; + + for (int i = 0; i < unspentOutputs.size(); ++i) { + byte[] newTX = BTCUtils.buildTXForSign(myAddress, outputAddress, changeAddress, unspentOutputs, i, amount, change); + + byte[] hashData = Util.calculateSHA256(newTX); + byte[] doubleHashData = Util.calculateSHA256(hashData); + + unspentOutputs.get(i).bodyDoubleHash = doubleHashData; + unspentOutputs.get(i).bodyHash = hashData; + + if(mCard.getSigningMethod() == Tangem_Card.SigningMethod.Sign_Raw || mCard.getSigningMethod() == Tangem_Card.SigningMethod.Sign_Raw_Validated_By_Issuer) + { + dataForSign[i] = newTX; + } + else + { + dataForSign[i] = doubleHashData; + } + + } + + byte[] signFromCard = null; + if(mCard.getSigningMethod() == Tangem_Card.SigningMethod.Sign_Raw || mCard.getSigningMethod() == Tangem_Card.SigningMethod.Sign_Raw_Validated_By_Issuer) + { + ByteArrayOutputStream bs = new ByteArrayOutputStream(); + if (dataForSign.length > 10) throw new Exception("To much hashes in one transaction!"); + for (int i = 0; i < dataForSign.length; i++) { + if (i != 0 && dataForSign[0].length != dataForSign[i].length) + throw new Exception("Hashes length must be identical!"); + bs.write(dataForSign[i]); + } + signFromCard = protocol.run_SignRaw(PINStorage.getPIN2(), bs.toByteArray()).getTLV(TLV.Tag.TAG_Signature).Value; + } + else { + signFromCard = protocol.run_SignHashes(PINStorage.getPIN2(), dataForSign, mCard.getSigningMethod() == Tangem_Card.SigningMethod.Sign_Hash_Validated_By_Issuer, null, mCard.getIssuer()).getTLV(TLV.Tag.TAG_Signature).Value; + // TODO slice signFromCard to hashes.length parts + } + + LastSignStorage.setLastSignDate(mCard.getWallet(), new Date()); + + + for (int i = 0; i < unspentOutputs.size(); ++i) { + BigInteger r = new BigInteger(1, Arrays.copyOfRange(signFromCard, 0 + i * 64, 32 + i * 64)); + BigInteger s = new BigInteger(1, Arrays.copyOfRange(signFromCard, 32 + i * 64, 64 + i * 64)); + s = CryptoUtil.toCanonicalised(s); + byte[] encodingSign = DerEncodingUtil.packSignDer(r, s, pbKey); + + unspentOutputs.get(i).scriptForBuild = encodingSign; + } + + byte[] realTX = BTCUtils.buildTXForSend(outputAddress, changeAddress, unspentOutputs, amount, change); + return realTX; + } +} diff --git a/app/src/main/java/com/tangem/wallet/ByteUtil.java b/app/src/main/java/com/tangem/wallet/ByteUtil.java new file mode 100644 index 0000000000..2d27915fc0 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/ByteUtil.java @@ -0,0 +1,39 @@ +package com.tangem.wallet; + +public class ByteUtil { + + public static final byte[] EMPTY_BYTE_ARRAY = new byte[0]; + public static byte[] and(byte[] b1, byte[] b2) { + if (b1.length != b2.length) throw new RuntimeException("Array sizes differ"); + byte[] ret = new byte[b1.length]; + for (int i = 0; i < ret.length; i++) { + ret[i] = (byte) (b1[i] & b2[i]); + } + return ret; + } + + public static byte[] or(byte[] b1, byte[] b2) { + if (b1.length != b2.length) throw new RuntimeException("Array sizes differ"); + byte[] ret = new byte[b1.length]; + for (int i = 0; i < ret.length; i++) { + ret[i] = (byte) (b1[i] | b2[i]); + } + return ret; + } + + public static boolean isNullOrZeroArray(byte[] array){ + return (array == null) || (array.length == 0); + } + + public static boolean isSingleZero(byte[] array){ + return (array.length == 1 && array[0] == 0); + } + + public static int length(byte[]... bytes) { + int result = 0; + for (byte[] array : bytes) { + result += (array == null) ? 0 : array.length; + } + return result; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/wallet/CardInfoActivity.java b/app/src/main/java/com/tangem/wallet/CardInfoActivity.java new file mode 100644 index 0000000000..78f53030f1 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/CardInfoActivity.java @@ -0,0 +1,122 @@ +package com.tangem.wallet; + +import android.os.Bundle; +import android.support.design.widget.TabLayout; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentManager; +import android.support.v4.app.FragmentPagerAdapter; +import android.support.v4.view.ViewPager; +import android.support.v7.app.AppCompatActivity; +import android.view.Menu; +import android.view.MenuItem; + +public class CardInfoActivity extends AppCompatActivity implements WalletInfoFragment.OnFragmentInteractionListener { + + /** + * The {@link android.support.v4.view.PagerAdapter} that will provide + * fragments for each of the sections. We use a + * {@link FragmentPagerAdapter} derivative, which will keep every + * loaded fragment in memory. If this becomes too memory intensive, it + * may be best to switch to a + * {@link android.support.v4.app.FragmentStatePagerAdapter}. + */ + private SectionsPagerAdapter mSectionsPagerAdapter; + + /** + * The {@link ViewPager} that will host the section contents. + */ + private ViewPager mViewPager; + + + private Tangem_Card mCard; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_card_info); + + // Create the adapter that will return a fragment for each of the three + // primary sections of the activity. + mSectionsPagerAdapter = new SectionsPagerAdapter(getSupportFragmentManager()); + + // Set up the ViewPager with the sections adapter. + mViewPager = (ViewPager) findViewById(R.id.container); + mViewPager.setAdapter(mSectionsPagerAdapter); + + TabLayout tabLayout = (TabLayout) findViewById(R.id.tabs); + tabLayout.setupWithViewPager(mViewPager); + + String UID = getIntent().getStringExtra("UID"); + mCard = new Tangem_Card(UID); + mCard.LoadFromBundle(getIntent().getBundleExtra("Card")); + + } + + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + // Inflate the menu; this adds items to the action bar if it is present. + getMenuInflater().inflate(R.menu.menu_card_info, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + // Handle action bar item clicks here. The action bar will + // automatically handle clicks on the Home/Up button, so long + // as you specify a parent activity in AndroidManifest.xml. + int id = item.getItemId(); + + //noinspection SimplifiableIfStatement + if (id == R.id.action_settings) { + return true; + } + + return super.onOptionsItemSelected(item); + } + + + /** + * A {@link FragmentPagerAdapter} that returns a fragment corresponding to + * one of the sections/tabs/pages. + */ + public class SectionsPagerAdapter extends FragmentPagerAdapter { + + public SectionsPagerAdapter(FragmentManager fm) { + super(fm); + } + + @Override + public Fragment getItem(int position) { + // getItem is called to instantiate the fragment for the given page. + // Return a PlaceholderFragment (defined as a static inner class below). + if (position == 0) { + return WalletInfoFragment.newInstance(mCard); + } /*else if (position == 1) { + return WalletUnspentFragment.newInstance(mCard); + } else if (position == 2) { + return WalletHistoryFragment.newInstance(mCard); + }*/ + return null; + } + + @Override + public int getCount() { + // Show 3 total pages. + return 3; + } + + @Override + public CharSequence getPageTitle(int position) { + switch (position) { + case 0: + return "Wallet info"; + case 1: + return "Unspent"; + case 2: + return "History"; + } + return null; + } + } +} diff --git a/app/src/main/java/com/tangem/wallet/CardListAdapter.java b/app/src/main/java/com/tangem/wallet/CardListAdapter.java new file mode 100644 index 0000000000..bc3668bb3c --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/CardListAdapter.java @@ -0,0 +1,599 @@ +package com.tangem.wallet; + +import android.content.Context; +import android.graphics.Color; +import android.os.Bundle; +import android.support.v7.widget.CardView; +import android.support.v7.widget.RecyclerView; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.RelativeLayout; +import android.widget.TextView; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import static com.tangem.wallet.Tangem_Card.CountOurTx; + +public class CardListAdapter extends RecyclerView.Adapter { + + + public static class CardViewHolder extends RecyclerView.ViewHolder { + CardView cv; + TextView tvBalance, tvOffline, tvBalanceEquivalent, tvWallet, tvInputs, tvStatusInBlockchain, tvCardID, tvLastInput, lbLastInput, lbLastOutput, tvLastOutput, tvBlockchain; + TextView tvType, tvTypeBg, tvVoid; + ImageView imgBlockchain, imgSecurityNotification; + View llCardLoaded, llCardEmpty, llCardError, llCardPurged; + + CardViewHolder(View itemView) { + super(itemView); + cv = itemView.findViewById(R.id.cvCard); + tvBalance = itemView.findViewById(R.id.tvBalance); + tvOffline = itemView.findViewById(R.id.tvOffline); + tvBalanceEquivalent = itemView.findViewById(R.id.tvBalanceEquivalent); + tvWallet = itemView.findViewById(R.id.tvWallet); + tvCardID = itemView.findViewById(R.id.tvCardID); + + tvType = itemView.findViewById(R.id.tvType); + tvTypeBg = itemView.findViewById(R.id.tvTypeBg); + tvVoid = itemView.findViewById(R.id.tvVoid); + + if (tvCardID != null) { + tvCardID.requestFocus(); + } + + tvInputs = itemView.findViewById(R.id.tvInputs); + + tvLastInput = itemView.findViewById(R.id.tvLastInput); + lbLastInput = itemView.findViewById(R.id.lbLastInput); + + lbLastOutput = itemView.findViewById(R.id.lbLastOutput); + tvLastOutput = itemView.findViewById(R.id.tvLastOutput); + + tvBlockchain = itemView.findViewById(R.id.tvBlockchain); + imgBlockchain = itemView.findViewById(R.id.imgBlockchain); + llCardLoaded = itemView.findViewById(R.id.cardLoaded); + llCardEmpty = itemView.findViewById(R.id.cardEmpty); + llCardError = itemView.findViewById(R.id.cardError); + llCardPurged = itemView.findViewById(R.id.cardPurged); + imgSecurityNotification = itemView.findViewById(R.id.imgSecurityNotification); + } + } + + public interface UiCallbacks { + void onViewCard(Bundle cardInfo); + } + + private LayoutInflater mLayoutInflater; + private Context mContext; + private UiCallbacks mUiCallbacks; + private List mCards = new ArrayList<>(100); + + public CardListAdapter(LayoutInflater layoutInflater, Bundle instate, UiCallbacks uiCallbacks) { + mLayoutInflater = layoutInflater; + mContext = layoutInflater.getContext(); + mUiCallbacks = uiCallbacks; + if (instate != null) { + // restore state + + ArrayList UIDs = instate.getStringArrayList("card_UID"); + + if( UIDs!=null ) { + for (String UID : UIDs) { + Tangem_Card card = new Tangem_Card(UID); + card.LoadFromBundle(instate.getBundle(String.format("card_%s", UID))); + mCards.add(card); + } + } + } + } + + + public void onSaveInstanceState(Bundle outstate) { + ArrayList UIDs = new ArrayList(mCards.size()); + for (Tangem_Card card : mCards) { + Bundle B = new Bundle(); + card.SaveToBundle(B); + outstate.putBundle(String.format("card_%s", card.getUID()), B); + UIDs.add(card.getUID()); + } + outstate.putStringArrayList("card_UID", UIDs); + } + + public void clearCards() { + mCards.clear(); + notifyDataSetChanged(); + } + + public Tangem_Card getCard(int cardIndex) { + return mCards.get(cardIndex); + } + + public Tangem_Card getCardByWallet(String walletAddress) { + for (Tangem_Card c : mCards) { + if (c.getWallet().equals(walletAddress)) { + return c; + } + } + return null; + } + + public void addCard(Tangem_Card card) { + mCards.add(0,card); + notifyDataSetChanged(); + } + + public void removeCard(int cardIndex) { + mCards.remove(cardIndex); + notifyItemRemoved(cardIndex); + } + + public void removeCard(Tangem_Card card) { + int i; + for (i = 0; i < mCards.size(); i++) { + if (Arrays.equals(mCards.get(i).getCID(), card.getCID())) { + break; + } + } + + if (i == mCards.size()) return; + + removeCard(i); + } + + + public void updateCard(Tangem_Card card) { + int i; + for (i = 0; i < mCards.size(); i++) { + if (Arrays.equals(mCards.get(i).getCID(), card.getCID())) { + break; + } + } + + if (i == mCards.size()) { + mCards.add(card); + } else { + mCards.remove(i); + mCards.add(i, card); + } + notifyDataSetChanged(); + } + + public void UpdateWalletBalance(String walletAddress, Long balanceConfirmed, Long balanceUnconfirmed, String validationNodeDescription) { + for (Tangem_Card c : mCards) { + if (c.getWallet().equals(walletAddress)) { + c.setBalanceConfirmed(balanceConfirmed); + c.setBalanceUnconfirmed(balanceUnconfirmed); + c.setDecimalBalance(String.valueOf(balanceConfirmed)); + c.setValidationNodeDescription(validationNodeDescription); + notifyDataSetChanged(); + return; + } + } + } + + + public void UpdateWalletCoutConfirmTx(String walletAddress, BigInteger nonce) { + for (Tangem_Card c : mCards) { + if (c.getWallet().equals(walletAddress)) { + c.SetConfirmTXCount(nonce); + notifyDataSetChanged(); + return; + } + } + } + + public void UpdateWalletBlockchain(String walletAddress, Blockchain blockchain) { + for (Tangem_Card c : mCards) { + if (c.getWallet().equals(walletAddress)) { + c.setBlockchainID(blockchain.getID()); + notifyDataSetChanged(); + return; + } + } + } + + public void AddWalletBlockchainNameToken(String walletAddress) { + for (Tangem_Card c : mCards) { + if (c.getWallet().equals(walletAddress)) { + c.addTokenToBlockchainName(); + notifyDataSetChanged(); + return; + } + } + } + + public void UpdateWalletBalance(String walletAddress, Long balanceConfirmed, String balanceString, String validationNodeDescription) { + for (Tangem_Card c : mCards) { + if (c.getWallet().equals(walletAddress)) { + c.setBalanceConfirmed(balanceConfirmed); + c.setBalanceUnconfirmed(0L); + c.setDecimalBalance(String.valueOf(balanceString)); + c.setValidationNodeDescription(validationNodeDescription); + notifyDataSetChanged(); + return; + } + } + } + + public void UpdateWalletBalanceOnlyAlter(String walletAddress, String balanceAlter) { + for (Tangem_Card c : mCards) { + if (c.getWallet().equals(walletAddress)) { + c.setDecimalBalanceAlter(balanceAlter); + notifyDataSetChanged(); + return; + } + } + } + + public void UpdateWalletUnspent(String walletAddress, JSONArray jsUnspentArray) { + for (Tangem_Card c : mCards) { + if (c.getWallet().equals(walletAddress)) { + try { + c.getUnspentTransactions().clear(); + for (int i = 0; i < jsUnspentArray.length(); i++) { + JSONObject jsUnspent = jsUnspentArray.getJSONObject(i); + Tangem_Card.UnspentTransaction trUnspent = new Tangem_Card.UnspentTransaction(); + trUnspent.txID = jsUnspent.getString("tx_hash"); + trUnspent.Amount = jsUnspent.getInt("value"); + trUnspent.Height = jsUnspent.getInt("height"); + c.getUnspentTransactions().add(trUnspent); + } + } catch (JSONException e) { + e.printStackTrace(); + } + notifyDataSetChanged(); + return; + } + } + } + + public void UpdateWalletHistory(String walletAddress, JSONArray jsHistoryArray) { + for (Tangem_Card c : mCards) { + if (c.getWallet().equals(walletAddress)) { + try { + c.getHistoryTransactions().clear(); + for (int i = 0; i < jsHistoryArray.length(); i++) { + JSONObject jsUnspent = jsHistoryArray.getJSONObject(i); + Tangem_Card.HistoryTransaction trHistory = new Tangem_Card.HistoryTransaction(); + trHistory.txID = jsUnspent.getString("tx_hash"); + trHistory.Height = jsUnspent.getInt("height"); + c.getHistoryTransactions().add(trHistory); + } + } catch (JSONException e) { + e.printStackTrace(); + } + notifyDataSetChanged(); + return; + } + } + } + + public void UpdateWalletHeader(String walletAddress, JSONObject jsHeader) { + for (Tangem_Card c : mCards) { + if (c.getWallet().equals(walletAddress)) { + try { + c.getHaedersInfo(); + c.UpdateHeaderInfo(new Tangem_Card.HeaderInfo( + jsHeader.getInt("block_height"), + jsHeader.getInt("timestamp"))); + } catch (JSONException e) { + e.printStackTrace(); + } + notifyDataSetChanged(); + return; + } + } + } + + public void UpdateRate(String walletAddress, float rate) { + for (Tangem_Card c : mCards) { + if (c.getWallet().equals(walletAddress)) { + c.setRate(rate); + notifyDataSetChanged(); + break; + } + } + } + + public void UpdateRateAlter(String walletAddress, float rate) { + for (Tangem_Card c : mCards) { + if (c.getWallet().equals(walletAddress)) { + c.setRateAlter(rate); + notifyDataSetChanged(); + break; + } + } + } + + public void UpdateTransaction(String walletAddress, String txHash, String raw) { + for (Tangem_Card c : mCards) { + if (c.getWallet().equals(walletAddress)) { + + List listTx = c.getUnspentTransactions(); + for (Tangem_Card.UnspentTransaction tx : listTx) { + if (tx.txID.equals(txHash)) { + tx.Raw = raw; + } + } + + List listHTx = c.getHistoryTransactions(); + for (Tangem_Card.HistoryTransaction tx : listHTx) { + if (tx.txID.equals(txHash)) { + tx.Raw = raw; + CountOurTx(listHTx); + } + } + notifyDataSetChanged(); + return; + } + } + } + + public void UpdateWalletError(String walletAddress, String error) { + for (Tangem_Card c : mCards) { + if (c.getWallet().equals(walletAddress)) { + c.setError(error); + notifyDataSetChanged(); + return; + } + } + } + + @Override + public int getItemCount() { + return mCards == null ? 0 : mCards.size(); + } + + @Override + public CardViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.card_list_item, parent, false); + CardViewHolder cvh = new CardViewHolder(v); + return cvh; + } + + @Override + public void onBindViewHolder(CardViewHolder holder, int position) { + + try { + Tangem_Card card = mCards.get(position); + CoinEngine engine = CoinEngineFactory.Create(card.getBlockchain()); + + int color = android.R.color.black; + + switch (card.getStatus()) { + case NotPersonalized: + color = R.color.card_state_error; + holder.llCardError.setVisibility(View.VISIBLE); + holder.llCardLoaded.setVisibility(View.GONE); + holder.llCardEmpty.setVisibility(View.GONE); + holder.llCardPurged.setVisibility(View.GONE); + + holder.imgBlockchain.setVisibility(View.INVISIBLE); + holder.imgSecurityNotification.setVisibility(View.INVISIBLE); + holder.tvCardID.setVisibility(View.INVISIBLE); + holder.tvCardID.setError(null); + + holder.tvType.setVisibility(View.INVISIBLE); + holder.tvTypeBg.setVisibility(View.INVISIBLE); + holder.tvVoid.setVisibility(View.INVISIBLE); + break; + case Empty: + color = R.color.card_state_empty; + holder.llCardEmpty.setVisibility(View.VISIBLE); + holder.llCardLoaded.setVisibility(View.GONE); + holder.llCardError.setVisibility(View.GONE); + holder.llCardPurged.setVisibility(View.GONE); + holder.imgBlockchain.setVisibility(View.VISIBLE); + if (card.getBlockchain() != null) { + holder.imgBlockchain.setImageResource(card.getBlockchain().getImageResource(this.mContext, card.getTokenSymbol())); + holder.imgBlockchain.setVisibility(View.VISIBLE); + } else { + holder.imgBlockchain.setVisibility(View.INVISIBLE); + } + if (card.useDefaultPIN1() || card.useDefaultPIN2() || card.getPauseBeforePIN2() > 0 || card.useDevelopersFirmware()) { + //holder.imgSecurityNotification.setVisibility(View.VISIBLE); + } else { + holder.imgSecurityNotification.setVisibility(View.INVISIBLE); + } + holder.tvCardID.setText(card.getCIDDescription()); + holder.tvCardID.setError(null); + holder.tvCardID.setVisibility(View.VISIBLE); + + holder.tvType.setVisibility(View.VISIBLE); + holder.tvTypeBg.setVisibility(View.VISIBLE); + if( card.isReusable() ) { + holder.tvType.setText(" REUSABLE REUSABLE "); + holder.tvTypeBg.setBackgroundColor(mContext.getResources().getColor(R.color.type_wallet, mContext.getTheme())); + }else{ + holder.tvType.setText(" BANKNOTE BANKNOTE"); + holder.tvTypeBg.setBackgroundColor(mContext.getResources().getColor(R.color.type_banknote, mContext.getTheme())); + } + holder.tvVoid.setVisibility(View.INVISIBLE); + break; + case Purged: + color = R.color.card_state_purged; + holder.llCardEmpty.setVisibility(View.GONE); + holder.llCardLoaded.setVisibility(View.GONE); + holder.llCardError.setVisibility(View.GONE); + holder.llCardPurged.setVisibility(View.VISIBLE); + holder.imgBlockchain.setVisibility(View.VISIBLE); + if (card.getBlockchain() != null) { + holder.imgBlockchain.setImageResource(card.getBlockchain().getImageResource(this.mContext, card.getTokenSymbol())); + holder.imgBlockchain.setVisibility(View.VISIBLE); + } else { + holder.imgBlockchain.setVisibility(View.INVISIBLE); + } + holder.imgSecurityNotification.setVisibility(View.INVISIBLE); + holder.tvCardID.setText(card.getCIDDescription()); + holder.tvCardID.setVisibility(View.VISIBLE); + holder.tvCardID.setError(null); + holder.tvType.setVisibility(View.VISIBLE); + holder.tvTypeBg.setVisibility(View.VISIBLE); + holder.tvType.setText(" BANKNOTE BANKNOTE"); + holder.tvTypeBg.setBackgroundColor(mContext.getResources().getColor(R.color.type_banknote, mContext.getTheme())); + holder.tvVoid.setVisibility(View.INVISIBLE); + break; + case Loaded: + holder.llCardLoaded.setVisibility(View.VISIBLE); + holder.llCardError.setVisibility(View.GONE); + holder.llCardEmpty.setVisibility(View.GONE); + holder.llCardPurged.setVisibility(View.GONE); + holder.tvCardID.setText(card.getCIDDescription()); + holder.tvCardID.setVisibility(View.VISIBLE); + holder.tvWallet.setText(card.getShortWalletString()); + + holder.tvBlockchain.setText(card.getBlockchainName()); + + if (card.getBlockchain() != null) { + holder.imgBlockchain.setImageResource(card.getBlockchain().getImageResource(this.mContext, card.getTokenSymbol())); + holder.imgBlockchain.setVisibility(View.VISIBLE); + } else { + holder.imgBlockchain.setVisibility(View.INVISIBLE); + } + + if (!engine.HasBalanceInfo(card)) { + color = R.color.card_state_loaded; + } else if (engine.IsBalanceNotZero(card)) { + color = R.color.card_state_loaded_with_coins; + } else { + color = R.color.card_state_loaded_with_zero; + } + + if (holder.tvStatusInBlockchain != null) { + if (card.hasBalanceInfo() && card.hasUnspentInfo() && card.getUnspentTransactions().size() > 0) { + holder.tvStatusInBlockchain.setText("Ok"); + holder.tvStatusInBlockchain.setTextColor(mContext.getResources().getColor(R.color.confirmed, mContext.getTheme())); + } else if (!card.hasBalanceInfo() || !card.hasUnspentInfo()) { + holder.tvStatusInBlockchain.setText("-- -- --"); + holder.tvStatusInBlockchain.setTextColor(mContext.getResources().getColor(R.color.primary_dark, mContext.getTheme())); + } else { + holder.tvStatusInBlockchain.setText("Not found"); + holder.tvStatusInBlockchain.setTextColor(mContext.getResources().getColor(R.color.not_confirmed, mContext.getTheme())); + } + } + + if ( engine.HasBalanceInfo(card) || card.getOfflineBalance() == null) { + String balance = engine.GetBalance(card); + holder.tvBalance.setText(balance); + holder.tvBalanceEquivalent.setText(engine.GetBalanceEquivalent(card)); + holder.tvBalance.setTextColor(Color.BLACK); + holder.tvOffline.setVisibility(View.INVISIBLE); + + } else { + + String offlineAmount = engine.ConvertByteArrayToAmount(card, card.getOfflineBalance()); + holder.tvBalance.setText(engine.GetAmountDescription(card, offlineAmount)); + holder.tvBalanceEquivalent.setText(engine.GetAmountEqualentDescriptor(card, offlineAmount)); + holder.tvOffline.setVisibility(View.VISIBLE); + } + if (!card.getAmountEquivalentDescriptionAvailable()) { + //holder.tvBalanceEquivalent.setError("Service unavailable"); + } else { + holder.tvBalanceEquivalent.setError(null); + } + + String error = card.getError(); + //holder.tvCardID.setError(error); + + if (holder.tvInputs != null) { + holder.tvInputs.setText(card.getInputsDescription()); + } + + boolean visibleFlag = engine != null ? engine.InOutPutVisible() : true; + int visibleIOPuts = visibleFlag ? View.VISIBLE : View.GONE; + if (holder.tvLastInput != null) { + holder.tvLastInput.setText(card.getLastInputDescription()); + if (card.getLastInputDescription().contains("awaiting")) { + holder.tvLastInput.setTextColor(mContext.getResources().getColor(R.color.not_confirmed, mContext.getTheme())); + } else if (card.getLastInputDescription().contains("None") || card.getLastInputDescription().contains("--")) { + holder.tvLastInput.setTextColor(mContext.getResources().getColor(R.color.primary_dark, mContext.getTheme())); + } else { + holder.tvLastInput.setTextColor(mContext.getResources().getColor(R.color.confirmed, mContext.getTheme())); + } + holder.tvLastInput.setVisibility(visibleIOPuts); + } + + if (holder.lbLastInput != null) { + holder.lbLastInput.setVisibility(visibleIOPuts); + } + + if (holder.tvLastOutput != null) { + holder.tvLastOutput.setText(card.getLastOutputDescription()); + holder.tvLastOutput.setVisibility(visibleIOPuts); + } + + if (holder.lbLastOutput != null) { + holder.lbLastOutput.setVisibility(visibleIOPuts); + } + + if (card.useDefaultPIN1() || card.useDefaultPIN2() || card.getPauseBeforePIN2() > 0 || card.useDevelopersFirmware()) { + //holder.imgSecurityNotification.setVisibility(View.VISIBLE); + } else { + holder.imgSecurityNotification.setVisibility(View.INVISIBLE); + } + + holder.tvType.setVisibility(View.VISIBLE); + holder.tvTypeBg.setVisibility(View.VISIBLE); + if( card.isReusable() ) { + holder.tvType.setText(" REUSABLE REUSABLE "); + holder.tvTypeBg.setBackgroundColor(mContext.getResources().getColor(R.color.type_wallet, mContext.getTheme())); + holder.tvVoid.setVisibility(View.INVISIBLE); + }else{ + holder.tvType.setText(" BANKNOTE BANKNOTE"); + + if( card.getRemainingSignatures()!=card.getMaxSignatures() ) { + holder.tvVoid.setVisibility(View.VISIBLE); + holder.tvTypeBg.setBackgroundColor(mContext.getResources().getColor(R.color.msg_err, mContext.getTheme())); + }else{ + holder.tvVoid.setVisibility(View.INVISIBLE); + holder.tvTypeBg.setBackgroundColor(mContext.getResources().getColor(R.color.type_banknote, mContext.getTheme())); + } + } + + break; + } + + if(card.useDevelopersFirmware()) { + holder.tvType.setText(" DEVELOPER KIT "); + holder.tvTypeBg.setBackgroundColor(mContext.getResources().getColor(R.color.fab, mContext.getTheme())); + holder.tvVoid.setVisibility(View.INVISIBLE); + } + + holder.cv.setOnClickListener(new CardClickListener(position)); + holder.cv.setCardBackgroundColor(mContext.getResources().getColor(color)); + + } catch (Exception e) { + Log.e("onBindViewHolder", e.toString()); + } + } + + private class CardClickListener implements View.OnClickListener { + Tangem_Card card; + int pos; + + CardClickListener(int position) { + card = mCards.get(position); + pos=position; + } + + @Override + public void onClick(View v) { + Bundle b = new Bundle(); + b.putString("UID", card.getUID()); + Bundle bCard = new Bundle(); + card.SaveToBundle(bCard); + b.putBundle("Card", bCard); + mUiCallbacks.onViewCard(b); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/wallet/CardUnspentListAdapter.java b/app/src/main/java/com/tangem/wallet/CardUnspentListAdapter.java new file mode 100644 index 0000000000..b5191ea7d1 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/CardUnspentListAdapter.java @@ -0,0 +1,83 @@ +package com.tangem.wallet; + +import android.content.Context; +import android.os.Build; +import android.text.Html; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.TextView; + +import static android.text.Html.FROM_HTML_MODE_COMPACT; + +/** + * Created by dvol on 17.07.2017. + */ + +public class CardUnspentListAdapter extends BaseAdapter { + private LayoutInflater mLayoutInflater; + private Context mContext; + private Tangem_Card mCard; + + public CardUnspentListAdapter(LayoutInflater layoutInflater, Tangem_Card card) { + mLayoutInflater = layoutInflater; + mContext = layoutInflater.getContext(); + mCard = card; + } + + @Override + public int getCount() { + if (mCard != null && mCard.getUnspentTransactions() != null) + return mCard.getUnspentTransactions().size(); + return 0; + } + + @Override + public Object getItem(int i) { + if (mCard != null && mCard.getUnspentTransactions() != null) + return mCard.getUnspentTransactions().get(i); + return null; + } + + @Override + public long getItemId(int i) { + if (mCard != null && mCard.getUnspentTransactions() != null) + return mCard.getUnspentTransactions().get(i).txID.hashCode(); + return 0; + } + + @Override + public View getView(int position, View convertView, ViewGroup viewGroup) { + if (convertView == null) { + convertView = mLayoutInflater.inflate(R.layout.card_unspent_list_item, viewGroup, + false); + } + TextView tvItem = (TextView) convertView.findViewById(R.id.tvItem); + + Tangem_Card.UnspentTransaction unspentTransaction = (Tangem_Card.UnspentTransaction) getItem(position); + + String html=String.format("%d mBTC
%s", unspentTransaction.Amount, unspentTransaction.txID); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + tvItem.setText(Html.fromHtml(html, FROM_HTML_MODE_COMPACT)); + } else { + tvItem.setText(Html.fromHtml(html.toString())); + } + return convertView; + } + + public void Clear() { + mCard.getUnspentTransactions().clear(); + notifyDataSetChanged(); + } + + public void UpdateUnspent(String tx_hash, int value, int height) { + Tangem_Card.UnspentTransaction newUT=new Tangem_Card.UnspentTransaction(); + newUT.txID=tx_hash; + newUT.Amount=value; + newUT.Height=height; + mCard.getUnspentTransactions().add(newUT); + notifyDataSetChanged(); + } +} diff --git a/app/src/main/java/com/tangem/wallet/CoinEngine.java b/app/src/main/java/com/tangem/wallet/CoinEngine.java new file mode 100644 index 0000000000..f56c71ab12 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/CoinEngine.java @@ -0,0 +1,82 @@ +package com.tangem.wallet; + +import android.net.Uri; + +import com.tangem.cardReader.CardProtocol; + +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; + +/** + * Created by Ilia on 15.02.2018. + */ + +public abstract class CoinEngine { + + public abstract String GetNextNode(Tangem_Card mCard); + + public abstract int GetNextNodePort(Tangem_Card mCard); + + public abstract String GetNode(Tangem_Card mCard); + + public abstract int GetNodePort(Tangem_Card mCard); + + public abstract void SwitchNode(Tangem_Card mCard); + + public abstract boolean AwaitingConfirmation(Tangem_Card card); + + public abstract boolean HasBalanceInfo(Tangem_Card card); + + public abstract boolean IsBalanceNotZero(Tangem_Card card); + + public abstract boolean IsBalanceAlterNotZero(Tangem_Card card); + + public abstract boolean CheckAmount(Tangem_Card card, String amount) throws Exception; + + public abstract int GetTokenDecimals(Tangem_Card card); + + public abstract String GetContractAddress(Tangem_Card card); + + public abstract byte[] Sign(String feeValue, String amountValue, String toValue, Tangem_Card mCard, CardProtocol protocol) throws Exception; + + public abstract boolean CheckUnspentTransaction(Tangem_Card mCard); + + public abstract Uri getShareWalletURIExplorer(Tangem_Card mCard); + + public abstract Long GetBalanceLong(Tangem_Card mCard); + + public abstract Uri getShareWalletURI(Tangem_Card mCard); + + public abstract String EvaluteFeeEquivalent(Tangem_Card mCard, String fee); + + public abstract boolean CheckAmountValie(Tangem_Card mCard, String amount, String fee, Long minFeeInInternalUnits); + + public abstract boolean InOutPutVisible(); + + public abstract String GetBalance(Tangem_Card mCard); + + public abstract String GetBalanceWithAlter(Tangem_Card mCard); + + public abstract String GetBalanceCurrency(Tangem_Card card); + + public abstract String GetFeeCurrency(); + + public abstract boolean IsNeedCheckNode(); + + public abstract String GetBalanceEquivalent(Tangem_Card mCard); + + public abstract String GetBalanceValue(Tangem_Card mCard); + + public abstract String GetAmountDescription(Tangem_Card mCard, String amount) throws Exception; + + public abstract String GetAmountEqualentDescriptor(Tangem_Card mCard, String value); + + public abstract boolean ValdateAddress(String address, Tangem_Card catd); + + public abstract String calculateAddress(Tangem_Card mCard, byte[] pkUncompressed) throws NoSuchProviderException, NoSuchAlgorithmException; + + public abstract String ConvertByteArrayToAmount(Tangem_Card mCard, byte[] bytes) throws Exception; + + public abstract byte[] ConvertAmountToByteArray(Tangem_Card mCard, String amount) throws Exception; + +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/wallet/CoinEngineFactory.java b/app/src/main/java/com/tangem/wallet/CoinEngineFactory.java new file mode 100644 index 0000000000..92c91713e9 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/CoinEngineFactory.java @@ -0,0 +1,23 @@ +package com.tangem.wallet; + +/** + * Created by Ilia on 15.02.2018. + */ + +public class CoinEngineFactory { + public static CoinEngine Create(Blockchain chain) + { + if(Blockchain.BitcoinCash == chain || Blockchain.BitcoinCashTestNet == chain) { + return new BtcCashEngine(); + }else if(Blockchain.Bitcoin == chain || Blockchain.BitcoinTestNet == chain) { + return new BtcEngine(); //TODO: ВРЕМЕНГГО!!!! + }else if(Blockchain.Ethereum == chain || Blockchain.EthereumTestNet == chain) { + return new EthEngine(); + } + else if(Blockchain.Token == chain) { + return new TokenEngine(); + } else { + return null; + } + } +} diff --git a/app/src/main/java/com/tangem/wallet/ConfirmPaymentActivity.java b/app/src/main/java/com/tangem/wallet/ConfirmPaymentActivity.java new file mode 100644 index 0000000000..0bb59e9f6b --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/ConfirmPaymentActivity.java @@ -0,0 +1,732 @@ +package com.tangem.wallet; + +import android.app.Activity; +import android.content.Intent; +import android.content.res.ColorStateList; +import android.graphics.Color; +import android.nfc.NfcAdapter; +import android.nfc.Tag; +import android.os.AsyncTask; +import android.os.Bundle; +import android.support.v4.widget.SwipeRefreshLayout; +import android.support.v7.app.AppCompatActivity; +import android.text.Editable; +import android.text.Html; +import android.text.Spanned; +import android.text.TextWatcher; +import android.util.Log; +import android.view.KeyEvent; +import android.view.View; +import android.widget.Button; +import android.widget.EditText; +import android.widget.ImageView; +import android.widget.ProgressBar; +import android.widget.RadioGroup; +import android.widget.TextView; +import android.widget.Toast; + +import com.tangem.cardReader.NfcManager; +import com.tangem.cardReader.Util; + +import org.json.JSONException; + +import java.io.IOException; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Calendar; +import java.util.Date; +import java.util.List; + +public class ConfirmPaymentActivity extends AppCompatActivity implements NfcAdapter.ReaderCallback { + + private static final int REQUEST_CODE_SIGN_PAYMENT = 1; + private static final int REQUEST_CODE_REQUEST_PIN2 = 2; + Button btnSend; + boolean feeRequestSuccess = false; + boolean balanceRequestSuccess = false; + EditText etWallet; + TextView tvCardID, tvBalance, tvCurrency, tvCurrency2, tvBalanceEquivalent, tvAmountEquivalent, tvFeeEquivalent; + EditText etAmount; + EditText etFee; + ImageView ivCamera; + Tangem_Card mCard; + RadioGroup rgFee; + String minFee = null, maxFee = null, normalFee = null; + Long minFeeInInternalUnits = 0L; + private NfcManager mNfcManager; + int requestPIN2Count = 0; + ProgressBar progressBar; + boolean nodeCheck = false; + Date dtVerifyed=null; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_confirm_payment); + + MainActivity.commonInit(getApplicationContext()); + mNfcManager = new NfcManager(this, this); + + mCard = new Tangem_Card(getIntent().getStringExtra("UID")); + mCard.LoadFromBundle(getIntent().getExtras().getBundle("Card")); + + progressBar = findViewById(R.id.progressBar); + + btnSend = findViewById(R.id.btnSend); + etWallet = findViewById(R.id.etWallet); + tvCardID = findViewById(R.id.tvCardID); + tvBalance = findViewById(R.id.tvBalance); + tvCurrency = findViewById(R.id.tvCurrency); + tvCurrency2 = findViewById(R.id.tvCurrency2); + etAmount = findViewById(R.id.etAmount); + etFee = findViewById(R.id.etFee); + tvBalanceEquivalent = findViewById(R.id.tvBalanceEquivalent); + tvAmountEquivalent = findViewById(R.id.tvAmountEquivalent); + + tvFeeEquivalent = findViewById(R.id.tvFeeEquivalent); + ivCamera = findViewById(R.id.ivCamera); + + rgFee = findViewById(R.id.rgFee); + rgFee.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(RadioGroup group, int checkedId) { + doSetFee(checkedId); + } + }); + + etAmount.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + try { + + CoinEngine engine = CoinEngineFactory.Create(mCard.getBlockchain()); + tvAmountEquivalent.setText(engine.GetAmountEqualentDescriptor(mCard, etAmount.getText().toString())); + if (!mCard.getAmountEquivalentDescriptionAvailable()) { + tvAmountEquivalent.setError("Service unavailable"); + } else { + tvAmountEquivalent.setError(null); + } + } catch (Exception e) { + e.printStackTrace(); + tvAmountEquivalent.setText(""); + } + } + + @Override + public void afterTextChanged(Editable s) { + + } + }); + + etFee.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + try { + + CoinEngine engine = CoinEngineFactory.Create(mCard.getBlockchain()); + String eqFee = engine.EvaluteFeeEquivalent(mCard, etFee.getText().toString()); + tvFeeEquivalent.setText(eqFee); + + if (!mCard.getAmountEquivalentDescriptionAvailable()) { + tvFeeEquivalent.setError("Service unavailable"); + } else { + tvFeeEquivalent.setError(null); + } + } catch (Exception e) { + e.printStackTrace(); + tvFeeEquivalent.setText(""); + } + } + + @Override + public void afterTextChanged(Editable s) { + + } + }); + + CoinEngine engine = CoinEngineFactory.Create(mCard.getBlockchain()); + //tvBalance.setText(engine.GetBalanceWithAlter(mCard)); + if (mCard.getBlockchain() == Blockchain.Token) { + Spanned html = Html.fromHtml(engine.GetBalanceWithAlter(mCard)); + tvBalance.setText(html); + } else { + tvBalance.setText(engine.GetBalanceWithAlter(mCard)); + } + etAmount.setText(getIntent().getStringExtra("Amount")); + tvCurrency.setText(engine.GetBalanceCurrency(mCard)); + tvCurrency2.setText(engine.GetFeeCurrency()); + + tvCardID.setText(mCard.getCIDDescription()); + + //tvBalanceEquivalent.setText(mCard.getBalanceEquivalentDescription()); + tvBalanceEquivalent.setText(engine.GetBalanceEquivalent(mCard)); + if (!mCard.getAmountEquivalentDescriptionAvailable()) { + tvBalanceEquivalent.setError("Service unavailable"); + } else { + tvBalanceEquivalent.setError(null); + } + + etWallet.setText(getIntent().getStringExtra("Wallet")); + + etFee.setText("?"); + + btnSend.setVisibility(View.INVISIBLE); + feeRequestSuccess = false; + balanceRequestSuccess = false; + btnSend.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + + Calendar calendar=Calendar.getInstance(); + calendar.add(Calendar.MINUTE,-1); + + if( dtVerifyed==null || dtVerifyed.before(calendar.getTime()) ) { + FinishActivityWithError(Activity.RESULT_CANCELED, "The obtained data is outdated! Try again"); + return; + } + + CoinEngine engineCoin = CoinEngineFactory.Create(mCard.getBlockchain()); + + if (engineCoin.IsNeedCheckNode() && !nodeCheck) { + Toast.makeText(getBaseContext(), "Cannot reach current active blockchain node. Try again", Toast.LENGTH_LONG).show(); + return; + } + String txFee = etFee.getText().toString(); + String txAmount = etAmount.getText().toString(); + + + if (!engineCoin.HasBalanceInfo(mCard)) { + FinishActivityWithError(Activity.RESULT_CANCELED, "Cannot check balance! No connection with blockchain nodes"); + return; + } else if (!engineCoin.IsBalanceNotZero(mCard)) { + FinishActivityWithError(Activity.RESULT_CANCELED, "The wallet is empty"); + return; + } else if(!engineCoin.CheckUnspentTransaction(mCard)) { + //else if (mCard.getUnspentTransactions().size() == 0 && mCard.getBlockchain() != Blockchain.Ethereum) { + FinishActivityWithError(Activity.RESULT_CANCELED, "Please wait for confirmation of incoming transaction"); + return; + } + + if(!engineCoin.CheckAmountValie(mCard, txAmount, txFee, minFeeInInternalUnits)) + { + FinishActivityWithError(Activity.RESULT_CANCELED, "Fee exceeds payment amount. Enter correct value and repeat sending."); + return; + } + + requestPIN2Count = 0; + Intent intent = new Intent(getBaseContext(), RequestPINActivity.class); + intent.putExtra("mode", RequestPINActivity.Mode.RequestPIN2.toString()); + intent.putExtra("UID", mCard.getUID()); + intent.putExtra("Card", mCard.getAsBundle()); + startActivityForResult(intent, REQUEST_CODE_REQUEST_PIN2); + } + }); + + if (mCard.getBlockchain() == Blockchain.Ethereum || mCard.getBlockchain() == Blockchain.EthereumTestNet || mCard.getBlockchain() == Blockchain.Token) { + ETHRequestTask task = new ETHRequestTask(mCard.getBlockchain()); + Infura_Request req = Infura_Request.GetGasPrise(mCard.getWallet()); + req.setID(67); + req.setBlockchain(mCard.getBlockchain()); + rgFee.setEnabled(false); + task.execute(req); + } else { + + rgFee.setEnabled(true); + + SharedData data = new SharedData(SharedData.COUNT_REQUEST); + + CoinEngine engineCoin = CoinEngineFactory.Create(mCard.getBlockchain()); + + for(int i =0 ; i < data.allRequest; ++i) { + + String nodeAddress = engineCoin.GetNextNode(mCard); + int nodePort = engineCoin.GetNextNodePort(mCard); + //ConnectTask connectTaskEx = new ConnectTask(Blockchain.getNextServiceHost(mCard), Blockchain.getNextServicePort(mCard), data); + ConnectTask connectTaskEx = new ConnectTask(nodeAddress, nodePort, data); + + //connectTaskEx.execute(Electrum_Request.CheckBalance(mCard.getWallet())); + connectTaskEx.executeOnExecutor( AsyncTask.THREAD_POOL_EXECUTOR,Electrum_Request.CheckBalance(mCard.getWallet())); + } + + String nodeAddress = engineCoin.GetNode(mCard); + int nodePort = engineCoin.GetNodePort(mCard); + ConnectTask connectTask = new ConnectTask(nodeAddress, nodePort, data); + + //ConnectTask connectTask = new ConnectTask(Blockchain.getServiceHost(mCard), Blockchain.getServicePort(mCard)); + + connectTask.execute(/*Electrum_Request.CheckBalance(mCard.getWallet()), */Electrum_Request.GetFee(mCard.getWallet())); + + int calcSize = 256; + try { + calcSize = BuildSize(etWallet.getText().toString(), "0.00", etAmount.getText().toString()); + } catch (Exception ex) { + Log.e("Build Fee error", ex.getMessage()); + } + + SharedData sharedFee = new SharedData(SharedData.COUNT_REQUEST); + + progressBar.setVisibility(View.VISIBLE); + for(int i = 0; i < SharedData.COUNT_REQUEST; ++i) + { + + ConnectFeeTask feeTask = new ConnectFeeTask(sharedFee); + + feeTask.execute(Fee_Request.GetFee(mCard.getWallet(), calcSize, Fee_Request.NORMAL), + Fee_Request.GetFee(mCard.getWallet(), calcSize, Fee_Request.MINIMAL), + Fee_Request.GetFee(mCard.getWallet(), calcSize, Fee_Request.PRIORITY)); + } + } + + } + + + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + switch (keyCode) { + case KeyEvent.KEYCODE_BACK: + Intent intent = new Intent(); + intent.putExtra("message", "Operation canceled"); + setResult(Activity.RESULT_CANCELED, intent); + finish(); + return true; + } + return super.onKeyDown(keyCode, event); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (requestCode == REQUEST_CODE_SIGN_PAYMENT) { + if (data != null && data.getExtras().containsKey("UID") && data.getExtras().containsKey("Card")) { + Tangem_Card updatedCard=new Tangem_Card(data.getStringExtra("UID")); + updatedCard.LoadFromBundle(data.getBundleExtra("Card")); + mCard=updatedCard; + } + if (resultCode == SignPaymentActivity.RESULT_INVALID_PIN && requestPIN2Count < 2) { + requestPIN2Count++; + Intent intent = new Intent(getBaseContext(), RequestPINActivity.class); + intent.putExtra("mode", RequestPINActivity.Mode.RequestPIN2.toString()); + intent.putExtra("UID", mCard.getUID()); + intent.putExtra("Card", mCard.getAsBundle()); + startActivityForResult(intent, REQUEST_CODE_REQUEST_PIN2); + return; + } + setResult(resultCode, data); + finish(); + } else if (requestCode == REQUEST_CODE_REQUEST_PIN2) { + if (resultCode == Activity.RESULT_OK) { + Intent intent = new Intent(getBaseContext(), SignPaymentActivity.class); + intent.putExtra("UID", mCard.getUID()); + intent.putExtra("Card", mCard.getAsBundle()); + intent.putExtra("Wallet", etWallet.getText().toString()); + intent.putExtra("Amount", etAmount.getText().toString()); + intent.putExtra("Fee", etFee.getText().toString()); + startActivityForResult(intent, REQUEST_CODE_SIGN_PAYMENT); + } else { + Toast.makeText(getBaseContext(), "PIN2 is required to sign the payment", Toast.LENGTH_LONG).show(); + } + } + } + + void FinishActivityWithError(int errorCode, String message) { + //Snackbar.make(etFee, message, Snackbar.LENGTH_LONG).show(); + Intent intent = new Intent(); + intent.putExtra("message", message); + setResult(errorCode, intent); + finish(); + } + + @Override + public void onTagDiscovered(Tag tag) { + try { + Log.w(getClass().getName(), "Ignore discovered tag!"); + mNfcManager.IgnoreTag(tag); + } catch (IOException e) { + e.printStackTrace(); + } + } + + int BuildSize(String outputAddress, String outFee, String outAmount) throws Exception { + String myAddress = mCard.getWallet(); + String changeAddress = myAddress; //"n2eMqTT929pb1RDNuqEnxdaLau1rxy3efi"; + byte[] pbKey = mCard.getWalletPublicKey(); + byte[] pbComprKey = mCard.getWalletPublicKeyRar(); + + // Build script for our address + List rawTxList = mCard.getUnspentTransactions(); + byte[] outputScriptWeAreAbleToSpend = Transaction.Script.buildOutput(myAddress).bytes; + + // Collect unspent + ArrayList unspentOutputs = BTCUtils.getOutputs(rawTxList, outputScriptWeAreAbleToSpend); + + long fullAmount = 0; + for (int i = 0; i < unspentOutputs.size(); ++i) { + fullAmount += unspentOutputs.get(i).value; + } + + // Get first unspent + UnspentOutputInfo outPut = unspentOutputs.get(0); + int outPutIndex = outPut.outputIndex; + + // get prev TX id; + String prevTXID = rawTxList.get(0).txID;//"f67b838d6e2c0c587f476f583843e93ff20368eaf96a798bdc25e01f53f8f5d2"; + + long fees = FormatUtil.ConvertStringToLong(outFee); + long amount = FormatUtil.ConvertStringToLong(outAmount); + amount = amount - fees; + + long change = fullAmount - fees - amount; + + if (amount + fees > fullAmount) { + throw new Exception(String.format("Balance (%d) < amount (%d) + (%d)", fullAmount, change, amount)); + } + + byte[][] hashesForSign = new byte[unspentOutputs.size()][]; + + for (int i = 0; i < unspentOutputs.size(); ++i) { + byte[] newTX = BTCUtils.buildTXForSign(myAddress, outputAddress, changeAddress, unspentOutputs, i, amount, change); + + byte[] hashData = Util.calculateSHA256(newTX); + byte[] doubleHashData = Util.calculateSHA256(hashData); + + Log.e("TX_BODY_1", BTCUtils.toHex(newTX)); + Log.e("TX_HASH_1", BTCUtils.toHex(hashData)); + Log.e("TX_HASH_2", BTCUtils.toHex(doubleHashData)); + + unspentOutputs.get(i).bodyDoubleHash = doubleHashData; + unspentOutputs.get(i).bodyHash = hashData; + + hashesForSign[i] = doubleHashData; + } + + byte[] signFromCard = new byte[64 * unspentOutputs.size()]; + + for (int i = 0; i < unspentOutputs.size(); ++i) { + BigInteger r = new BigInteger(1, Arrays.copyOfRange(signFromCard, 0 + i * 64, 32 + i * 64)); + BigInteger s = new BigInteger(1, Arrays.copyOfRange(signFromCard, 32 + i * 64, 64 + i * 64)); + byte[] encodingSign = DerEncodingUtil.packSignDer(r, s, pbKey); + unspentOutputs.get(i).scriptForBuild = encodingSign; + } + + byte[] realTX = BTCUtils.buildTXForSend(outputAddress, changeAddress, unspentOutputs, amount, change); + + return realTX.length; + } + + private class ConnectTask extends Electrum_Task { + public ConnectTask(String host, int port) { + super(host, port); + } + + public ConnectTask(String host, int port, SharedData sharedData) { + super(host, port, sharedData); + } + @Override + protected void onProgressUpdate(Integer... values) { + super.onProgressUpdate(values); + } + + @Override + protected void onPostExecute(List requests) { + super.onPostExecute(requests); + for (Electrum_Request request : requests) { + try { + if (request.error == null) { + if (request.isMethod(Electrum_Request.METHOD_GetBalance)) { + try { + etFee.setText("--"); + + //String mWalletAddress = request.getParams().getString(0); + if ((request.getResult().getInt("confirmed") + request.getResult().getInt("unconfirmed")) / mCard.getBlockchain().getMultiplier() * 1000000.0 < Float.parseFloat(etAmount.getText().toString())) { + etFee.setError("Not enough funds"); + balanceRequestSuccess = false; + btnSend.setVisibility(View.INVISIBLE); + dtVerifyed=null; + nodeCheck = false; + } else { + etFee.setError(null); + balanceRequestSuccess = true; + if(feeRequestSuccess && balanceRequestSuccess) { + btnSend.setVisibility(View.VISIBLE); + + } + dtVerifyed=new Date(); + nodeCheck = true; + } + } catch (JSONException e) { + if(sharedCounter != null) + { + int errCounter = sharedCounter.errorRequest.incrementAndGet(); + if(errCounter >= sharedCounter.allRequest) + { + e.printStackTrace(); + FinishActivityWithError(Activity.RESULT_CANCELED, "Cannot check balance! No connection with blockchain nodes"); + } + } + else + { + e.printStackTrace(); + FinishActivityWithError(Activity.RESULT_CANCELED, "Cannot check balance! No connection with blockchain nodes"); + } + } + } else if (request.isMethod(Electrum_Request.METHOD_GetFee)) { + if (request.getResultString() == "-1") { + etFee.setText("3"); + } + } + } else { +// etFee.setError(request.error); +// btnSend.setVisibility(View.INVISIBLE); + if(sharedCounter != null) + { + int errCounter = sharedCounter.errorRequest.incrementAndGet(); + if(errCounter >= sharedCounter.allRequest) + { + FinishActivityWithError(Activity.RESULT_CANCELED, "Cannot calculate fee! No connection with blockchain nodes"); + } + } + else + { + FinishActivityWithError(Activity.RESULT_CANCELED, "Cannot calculate fee! No connection with blockchain nodes"); + } + return; + } + } catch (JSONException e) { + if(sharedCounter != null) + { + int errCounter = sharedCounter.errorRequest.incrementAndGet(); + if(errCounter >= sharedCounter.allRequest) + { + e.printStackTrace(); + FinishActivityWithError(Activity.RESULT_CANCELED, "Cannot calculate fee! No connection with blockchain nodes"); + + } + } + else + { + e.printStackTrace(); + FinishActivityWithError(Activity.RESULT_CANCELED, "Cannot calculate fee! No connection with blockchain nodes"); + } + } + } + + } + } + + private class ETHRequestTask extends Infura_Task { + ETHRequestTask(Blockchain blockchain){ + super(blockchain); + } + @Override + protected void onPostExecute(List requests) { + super.onPostExecute(requests); + for (Infura_Request request : requests) { + try { + Long price = 0L; + if (request.error == null) { + + if (request.isMethod(Infura_Request.METHOD_ETH_GetGasPrice)) { + try { + String gasPrice = request.getResultString(); + gasPrice = gasPrice.substring(2); + BigInteger l = new BigInteger(gasPrice, 16); + + BigInteger m = mCard.getBlockchain() == Blockchain.Token ? BigInteger.valueOf(55000) : BigInteger.valueOf(21000); + l = l.multiply(m); + String feeInGwei = mCard.getAmountInGwei(String.valueOf(l)); + + minFee=feeInGwei; + maxFee=feeInGwei; + normalFee=feeInGwei; + etFee.setText(feeInGwei); + etFee.setError(null); + btnSend.setVisibility(View.VISIBLE); + feeRequestSuccess = true; + balanceRequestSuccess = true; + + dtVerifyed=new Date(); + minFeeInInternalUnits = mCard.InternalUnitsFromString(feeInGwei); + + } catch (JSONException e) { + e.printStackTrace(); + FinishActivityWithError(Activity.RESULT_CANCELED, "Can't calculate fee! No connection with blockchain nodes"); + } + } + } else { + FinishActivityWithError(Activity.RESULT_CANCELED, "Can't calculate fee! No connection with blockchain nodes"); + } + } catch (JSONException e) { + e.printStackTrace(); + FinishActivityWithError(Activity.RESULT_CANCELED, "Can't calculate fee! No connection with blockchain nodes"); + } + } + } + } + + private class ConnectFeeTask extends Fee_Task { + public ConnectFeeTask(SharedData sharedData) { + super(sharedData); + } + @Override + protected void onPostExecute(List requests) { + super.onPostExecute(requests); + for (Fee_Request request : requests) { + if (request.error == null) { + long minFeeRate = 0; + + try { + + try { + String tmpAnswer = request.getAsString(); + BigDecimal minFeeBD = new BigDecimal(tmpAnswer); + BigDecimal multiplicator = new BigDecimal("100000000"); + minFeeBD = minFeeBD.multiply(multiplicator); + BigInteger minFeeBI = minFeeBD.toBigInteger(); + minFeeRate = minFeeBI.longValue(); + } catch (Exception e) { + + if(sharedCounter != null) + { + int errCounter = sharedCounter.errorRequest.incrementAndGet(); + + + if(errCounter >= sharedCounter.allRequest) + { + progressBar.setVisibility(View.INVISIBLE); + FinishActivityWithError(Activity.RESULT_CANCELED, "Cannot calculate fee! No connection with blockchain nodes"); + } + } + else + { + progressBar.setVisibility(View.INVISIBLE); + FinishActivityWithError(Activity.RESULT_CANCELED, "Cannot calculate fee! No connection with blockchain nodes"); + } + + //FinishActivityWithError(Activity.RESULT_CANCELED, "Cannot calculate fee! No connection with blockchain nodes"); + return; + } + + if (minFeeRate == 0) { + progressBar.setVisibility(View.INVISIBLE); + FinishActivityWithError(Activity.RESULT_CANCELED, "Cannot calculate fee! Wrong data received from the node"); + return; + } + + long inputCount = request.txSize; + + if (inputCount != 0) { + minFeeRate = minFeeRate * inputCount; + } else { + minFeeRate = minFeeRate * 256; + } + } catch (Exception e) { + e.printStackTrace(); + if(sharedCounter != null) + { + int errCounter = sharedCounter.errorRequest.incrementAndGet(); + if(errCounter >= sharedCounter.allRequest) + { + progressBar.setVisibility(View.INVISIBLE); + FinishActivityWithError(Activity.RESULT_CANCELED, "Cannot calculate fee! No connection with blockchain nodes"); + } + } + else + { + progressBar.setVisibility(View.INVISIBLE); + FinishActivityWithError(Activity.RESULT_CANCELED, "Cannot calculate fee! No connection with blockchain nodes"); + } + return; + } + + progressBar.setVisibility(View.INVISIBLE); + + float finalFee = (float) minFeeRate / (float) 10000; + + finalFee = Math.round(finalFee) / (float) 10000; + + if (request.getBlockCount() == Fee_Request.MINIMAL) { + minFee = String.valueOf(finalFee); + minFeeInInternalUnits = mCard.InternalUnitsFromString(String.valueOf(finalFee)); + } else if (request.getBlockCount() == Fee_Request.NORMAL) { + normalFee = String.valueOf(finalFee); + } else if (request.getBlockCount() == Fee_Request.PRIORITY) { + maxFee = String.valueOf(finalFee); + } + + doSetFee(rgFee.getCheckedRadioButtonId()); + + etFee.setError(null); + feeRequestSuccess = true; + if(feeRequestSuccess && balanceRequestSuccess) { + btnSend.setVisibility(View.VISIBLE); + } + dtVerifyed=new Date(); + + } else { + + if(sharedCounter != null) + { + int errCounter = sharedCounter.errorRequest.incrementAndGet(); + if(errCounter >= sharedCounter.allRequest) + { + progressBar.setVisibility(View.INVISIBLE); + FinishActivityWithError(Activity.RESULT_CANCELED, "Cannot calculate fee! No connection with blockchain nodes"); + } + } + else + { + progressBar.setVisibility(View.INVISIBLE); + FinishActivityWithError(Activity.RESULT_CANCELED, "Cannot calculate fee! No connection with blockchain nodes"); + } + } + } + } + } + + private void doSetFee(int checkedRadioButtonId) { + switch (checkedRadioButtonId) { + case R.id.rbMinimalFee: + if (minFee != null) etFee.setText(minFee); + else etFee.setText("?"); + break; + case R.id.rbNormalFee: + if (normalFee != null) etFee.setText(normalFee); + else etFee.setText("?"); + break; + case R.id.rbMaximumFee: + if (maxFee != null) etFee.setText(maxFee); + else etFee.setText("?"); + break; + } + } + + @Override + public void onResume() { + super.onResume(); + mNfcManager.onResume(); + } + + @Override + public void onPause() { + super.onPause(); + mNfcManager.onPause(); + } + + @Override + public void onStop() { + super.onStop(); + mNfcManager.onStop(); + } +} diff --git a/app/src/main/java/com/tangem/wallet/CreateNewWalletActivity.java b/app/src/main/java/com/tangem/wallet/CreateNewWalletActivity.java new file mode 100644 index 0000000000..0c55114850 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/CreateNewWalletActivity.java @@ -0,0 +1,330 @@ +package com.tangem.wallet; + +import android.app.Activity; +import android.content.Intent; +import android.content.res.ColorStateList; +import android.graphics.Color; +import android.nfc.NfcAdapter; +import android.nfc.Tag; +import android.nfc.tech.IsoDep; +import android.os.Bundle; +import android.support.v7.app.AppCompatActivity; +import android.util.Log; +import android.view.View; +import android.widget.ProgressBar; +import android.widget.TextView; +import android.widget.Toast; + +import com.tangem.cardReader.CardProtocol; +import com.tangem.cardReader.NfcManager; +import com.tangem.cardReader.Util; + +public class CreateNewWalletActivity extends AppCompatActivity implements NfcAdapter.ReaderCallback, CardProtocol.Notifications { + + public static final int RESULT_INVALID_PIN = Activity.RESULT_FIRST_USER; + private Tangem_Card mCard; + private TextView tvCardID; + private NfcManager mNfcManager; + private static final String logTag = "CreateNewActivity"; + private ProgressBar progressBar; + private CreateNewWalletTask createNewWalletTask; + private boolean lastReadSuccess = true; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_create_new_wallet); + + MainActivity.commonInit(getApplicationContext()); + mCard = new Tangem_Card(getIntent().getStringExtra("UID")); + mCard.LoadFromBundle(getIntent().getExtras().getBundle("Card")); + + tvCardID = (TextView) findViewById(R.id.tvCardID); + tvCardID.setText(mCard.getCIDDescription()); + + mNfcManager = new NfcManager(this, this); + + progressBar = (ProgressBar) findViewById(R.id.progressBar); + progressBar.setProgressTintList(ColorStateList.valueOf(Color.DKGRAY)); + progressBar.setVisibility(View.INVISIBLE); + } + + @Override + public void onTagDiscovered(Tag tag) { + try { + // get IsoDep handle and run cardReader thread + final IsoDep isoDep = IsoDep.get(tag); + if (isoDep == null) { + throw new CardProtocol.TangemException(getString(R.string.wrong_tag_err)); + } + byte UID[] = tag.getId(); + String sUID = Util.byteArrayToHexString(UID); + Log.v(logTag, "UID: " + sUID); + + if (sUID.equals(mCard.getUID())) { + if (lastReadSuccess) { + isoDep.setTimeout(mCard.getPauseBeforePIN2() + 5000); + } else { + isoDep.setTimeout(mCard.getPauseBeforePIN2() + 65000); + } + createNewWalletTask = new CreateNewWalletTask(isoDep, this); + createNewWalletTask.start(); + } else { + Log.d(logTag, "Mismatch card UID (" + sUID + " instead of " + mCard.getUID() + ")"); + mNfcManager.IgnoreTag(isoDep.getTag()); + return; + } + + } catch (Exception e) { + e.printStackTrace(); + } + + } + + @Override + public void onResume() { + super.onResume(); + mNfcManager.onResume(); + } + + @Override + public void onPause() { + mNfcManager.onPause(); + if (createNewWalletTask != null) { + createNewWalletTask.cancel(true); + } + super.onPause(); + } + + @Override + public void onStop() { + // dismiss enable NFC dialog + mNfcManager.onStop(); + if (createNewWalletTask != null) { + createNewWalletTask.cancel(true); + } + super.onStop(); + } + +// @Override +// public Dialog CreateNFCDialog(int id, AlertDialogWrapper.Builder builder, LayoutInflater li) { +// return mNfcManager.ShowNFCEnableDialog(); //onCreateDialog(id, builder, li); +// } + + private class CreateNewWalletTask extends Thread { + + IsoDep mIsoDep; + CardProtocol.Notifications mNotifications; + private boolean isCancelled = false; + + public CreateNewWalletTask(IsoDep isoDep, CardProtocol.Notifications notifications) { + mIsoDep = isoDep; + mNotifications = notifications; + } + + @Override + public void run() { + if (mIsoDep == null) { + return; + } + CardProtocol protocol = new CardProtocol(getBaseContext(), mIsoDep, mCard, mNotifications); + mNotifications.OnReadStart(protocol); + try { + // for Samsung's bugs - + // Workaround for the Samsung Galaxy S5 (since the + // first connection always hangs on transceive). + int timeout = mIsoDep.getTimeout(); + mIsoDep.connect(); + mIsoDep.close(); + mIsoDep.connect(); + mIsoDep.setTimeout(timeout); + try { + mNotifications.OnReadProgress(protocol, 5); + + Log.i("CreateNewWalletTask", "[-- Start create new wallet --]"); + + if (isCancelled) return; + protocol.run_VerifyCard(); + + Log.i("CreateNewWalletTask", "Manufacturer: " + protocol.getCard().getManufacturer().getOfficialName()); + + mNotifications.OnReadProgress(protocol, 30); + if (isCancelled) return; + +// if (mCard.getPauseBeforePIN2() > 0) { +// mNotifications.OnReadWait(mCard.getPauseBeforePIN2()); +// } +// try { + protocol.run_CreateWallet(PINStorage.getPIN2()); +// } finally { +// mNotifications.OnReadWait(0); +// } + mNotifications.OnReadProgress(protocol, 60); + if (isCancelled) return; + + protocol.run_Read(); + + } finally { + mNfcManager.IgnoreTag(mIsoDep.getTag()); + } + } catch (Exception e) { + e.printStackTrace(); + protocol.setError(e); + + } finally { + Log.i("CreateNewWalletTask", "[-- Finish create new wallet --]"); + mNotifications.OnReadFinish(protocol); + } + } + + public void cancel(Boolean AllowInterrupt) { + try { + if (this.isAlive()) { + isCancelled = true; + join(500); + } + if (this.isAlive() && AllowInterrupt) { + interrupt(); + mNotifications.OnReadCancel(); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + } + + public void OnReadStart(CardProtocol cardProtocol) { + progressBar.post(new Runnable() { + @Override + public void run() { + progressBar.setVisibility(View.VISIBLE); + progressBar.setProgress(5); + } + }); + } + + public void OnReadFinish(final CardProtocol cardProtocol) { + + createNewWalletTask = null; + + if (cardProtocol != null) { + if (cardProtocol.getError() == null) { + progressBar.post(new Runnable() { + @Override + public void run() { + progressBar.setProgress(100); + progressBar.setProgressTintList(ColorStateList.valueOf(Color.GREEN)); + Intent intent = new Intent(); + intent.putExtra("UID", cardProtocol.getCard().getUID()); + intent.putExtra("Card", cardProtocol.getCard().getAsBundle()); + setResult(Activity.RESULT_OK, intent); + finish(); + } + }); + } else { + lastReadSuccess = false; + if (cardProtocol.getError() instanceof CardProtocol.TangemException_InvalidPIN) { + progressBar.post(new Runnable() { + @Override + public void run() { + progressBar.setProgress(100); + progressBar.setProgressTintList(ColorStateList.valueOf(Color.RED)); + } + }); + progressBar.postDelayed(new Runnable() { + @Override + public void run() { + try { + progressBar.setProgress(0); + progressBar.setProgressTintList(ColorStateList.valueOf(Color.DKGRAY)); + progressBar.setVisibility(View.INVISIBLE); + Intent intent = new Intent(); + intent.putExtra("message", "Cannot create wallet. Make sure you enter correct PIN2!"); + intent.putExtra("UID", cardProtocol.getCard().getUID()); + intent.putExtra("Card", cardProtocol.getCard().getAsBundle()); + setResult(RESULT_INVALID_PIN, intent); + finish(); + } catch (Exception e) { + e.printStackTrace(); + } + } + }, 500); + return; + } else { + progressBar.post(new Runnable() { + @Override + public void run() { + if (cardProtocol.getError() instanceof CardProtocol.TangemException_ExtendedLengthNotSupported) { + if (!NoExtendedLengthSupportDialog.allreadyShowed) { + new NoExtendedLengthSupportDialog().show(getFragmentManager(), "NoExtendedLengthSupportDialog"); + } + } else { + Toast.makeText(getBaseContext(), "Try to scan again", Toast.LENGTH_LONG).show(); + } + progressBar.setProgress(100); + progressBar.setProgressTintList(ColorStateList.valueOf(Color.RED)); + } + }); + + } + } + } + + progressBar.postDelayed(new Runnable() { + @Override + public void run() { + try { + progressBar.setProgress(0); + progressBar.setProgressTintList(ColorStateList.valueOf(Color.DKGRAY)); + progressBar.setVisibility(View.INVISIBLE); + } catch (Exception e) { + e.printStackTrace(); + } + } + }, 500); + } + + public void OnReadProgress(CardProtocol protocol, final int progress) { + progressBar.post(new Runnable() { + @Override + public void run() { + progressBar.setProgress(progress); + } + }); + } + + public void OnReadCancel() { + + createNewWalletTask = null; + + progressBar.postDelayed(new Runnable() { + @Override + public void run() { + try { + progressBar.setProgress(0); + progressBar.setProgressTintList(ColorStateList.valueOf(Color.DKGRAY)); + progressBar.setVisibility(View.INVISIBLE); + } catch (Exception e) { + e.printStackTrace(); + } + } + }, 500); + } + + @Override + public void OnReadWait(final int msec) { + WaitSecurityDelayDialog.OnReadWait(this, msec); + } + + @Override + public void OnReadBeforeRequest(int timeout) { + WaitSecurityDelayDialog.onReadBeforeRequest(this, timeout); + } + + @Override + public void OnReadAfterRequest() { + WaitSecurityDelayDialog.onReadAfterRequest(this); + } +} + diff --git a/app/src/main/java/com/tangem/wallet/CryptoUtil.java b/app/src/main/java/com/tangem/wallet/CryptoUtil.java new file mode 100644 index 0000000000..689d0d7eec --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/CryptoUtil.java @@ -0,0 +1,270 @@ +package com.tangem.wallet; + +import android.util.Log; + +import com.tangem.cardReader.Util; + +import org.spongycastle.asn1.ASN1EncodableVector; +import org.spongycastle.asn1.ASN1Integer; +import org.spongycastle.asn1.DERSequence; +import org.spongycastle.asn1.sec.SECNamedCurves; +import org.spongycastle.asn1.x9.X9ECParameters; +import org.spongycastle.asn1.x9.X9IntegerConverter; +import org.spongycastle.crypto.params.ECDomainParameters; +import org.spongycastle.crypto.params.ECPrivateKeyParameters; +import org.spongycastle.crypto.params.ECPublicKeyParameters; +import org.spongycastle.crypto.signers.ECDSASigner; +import org.spongycastle.jce.ECNamedCurveTable; +import org.spongycastle.jce.spec.ECNamedCurveParameterSpec; +import org.spongycastle.jce.spec.ECPublicKeySpec; +import org.spongycastle.math.ec.ECAlgorithms; +import org.spongycastle.math.ec.ECCurve; +import org.spongycastle.math.ec.ECPoint; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.math.BigInteger; +import java.security.InvalidKeyException; +import java.security.KeyFactory; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.PublicKey; +import java.security.Signature; +import java.security.SignatureException; +import java.security.spec.InvalidKeySpecException; +import java.util.Arrays; + +import static org.bitcoinj.core.ECKey.CURVE; +import static org.bitcoinj.core.ECKey.HALF_CURVE_ORDER; + +/** + * Created by Ilia on 15.02.2018. + */ + +public class CryptoUtil { + + public static boolean checkHashSign2(byte[] pub, byte[] hash, BigInteger r, BigInteger s) + { + ECDSASigner signer = new ECDSASigner(); + + ECPublicKeyParameters params = new ECPublicKeyParameters(CURVE.getCurve().decodePoint(pub), CURVE); + signer.init(false, params); + return signer.verifySignature(hash, r, s); + } + + public static boolean isCanonical(BigInteger s) { + return s.compareTo(HALF_CURVE_ORDER) <= 0; + } + + public static BigInteger toCanonicalised(BigInteger s) { + + // The order of the curve is the number of valid points that exist on that curve. If S is in the upper + // half of the number of valid points, then bring it back to the lower half. Otherwise, imagine that + // N = 10 + // s = 8, so (-8 % 10 == 2) thus both (r, 8) and (r, 2) are valid solutions. + // 10 - 8 == 2, giving us always the latter solution, which is canonical. + if(!isCanonical(s)) { + BigInteger canon = CURVE.getN().subtract(s); + Log.e("TX_SIGN", "non Canonical S"); + return canon; + } + + return s; + + } + + private static ECPoint decompressKey(BigInteger xBN, boolean yBit) { + X9IntegerConverter x9 = new X9IntegerConverter(); + byte[] compEnc = x9.integerToBytes(xBN, 1 + x9.getByteLength(CURVE.getCurve())); + compEnc[0] = (byte) (yBit ? 0x03 : 0x02); + return CURVE.getCurve().decodePoint(compEnc); + } + + public static byte[] recoverPubBytesFromSignature(int recId, ECDSASignature_ETH sig, byte[] messageHash) { + // 1.0 For j from 0 to h (h == recId here and the loop is outside this function) + // 1.1 Let x = r + jn + + X9ECParameters params = SECNamedCurves.getByName("secp256k1"); + ECDomainParameters CURVE2 = new ECDomainParameters(params.getCurve(), params.getG(), params.getN(), params.getH()); + + BigInteger n = CURVE2.getN(); // Curve order. + BigInteger i = BigInteger.valueOf((long) recId / 2); + BigInteger x = sig.r.add(i.multiply(n)); + // 1.2. Convert the integer x to an octet string X of length mlen using the conversion routine + // specified in Section 2.3.7, where mlen = ⌈(log2 p)/8⌉ or mlen = ⌈m/8⌉. + // 1.3. Convert the octet string (16 set binary digits)||X to an elliptic curve point R using the + // conversion routine specified in Section 2.3.4. If this conversion routine outputs “invalid”, then + // do another iteration of Step 1. + // + // More concisely, what these points mean is to use X as a compressed public key. + ECCurve.Fp curve = (ECCurve.Fp) CURVE2.getCurve(); + BigInteger prime = curve.getQ(); // Bouncy Castle is not consistent about the letter it uses for the prime. + if (x.compareTo(prime) >= 0) { + // Cannot have point co-ordinates larger than this as everything takes place modulo Q. + return null; + } + // Compressed keys require you to know an extra bit of data about the y-coord as there are two possibilities. + // So it's encoded in the recId. + ECPoint R = decompressKey(x, (recId & 1) == 1); + // 1.4. If nR != point at infinity, then do another iteration of Step 1 (callers responsibility). + if (!R.multiply(n).isInfinity()) + return null; + // 1.5. Compute e from M using Steps 2 and 3 of ECDSA signature verification. + BigInteger e = new BigInteger(1, messageHash); + // 1.6. For k from 1 to 2 do the following. (loop is outside this function via iterating recId) + // 1.6.1. Compute a candidate public key as: + // Q = mi(r) * (sR - eG) + // + // Where mi(x) is the modular multiplicative inverse. We transform this into the following: + // Q = (mi(r) * s ** R) + (mi(r) * -e ** G) + // Where -e is the modular additive inverse of e, that is z such that z + e = 0 (mod n). In the above equation + // ** is point multiplication and + is point addition (the EC group operator). + // + // We can find the additive inverse by subtracting e from zero then taking the mod. For example the additive + // inverse of 3 modulo 11 is 8 because 3 + 8 mod 11 = 0, and -3 mod 11 = 8. + BigInteger eInv = BigInteger.ZERO.subtract(e).mod(n); + BigInteger rInv = sig.r.modInverse(n); + BigInteger srInv = rInv.multiply(sig.s).mod(n); + BigInteger eInvrInv = rInv.multiply(eInv).mod(n); + ECPoint.Fp q = (ECPoint.Fp) ECAlgorithms.sumOfTwoMultiplies(CURVE2.getG(), eInvrInv, R, srInv); + return q.getEncoded(/* compressed */ false); + } + + public static byte[] calcSign(byte[] priv, byte[] hash) + { + ECDSASigner signer = new ECDSASigner(); + BigInteger d = new BigInteger(priv); + ECPrivateKeyParameters params = new ECPrivateKeyParameters(d, CURVE); + signer.init(true, params); + BigInteger[] rs = signer.generateSignature(hash); + byte[] r = rs[0].toByteArray(); + byte[] s = rs[1].toByteArray(); + byte[] sign = new byte[64]; + for(int i = 0; i < 32; ++i) + { + sign[i] = r[i]; + sign[i+32] = s[i]; + } + return sign; + + } + + public static boolean isEncodingCanonical(byte[] signature) { + // See Bitcoin Core's IsCanonicalSignature, https://bitcointalk.org/index.php?topic=8392.msg127623#msg127623 + // A canonical signature exists of: <30> <02> <02> + // Where R and S are not negative (their first byte has its highest bit not set), and not + // excessively padded (do not start with a 0 byte, unless an otherwise negative number follows, + // in which case a single 0 byte is necessary and even required). + if (signature.length < 9 || signature.length > 73) + return false; + + int hashType = (signature[signature.length-1] & 0xff) & ~0x80; // mask the byte to prevent sign-extension hurting us + if (hashType < 1 || hashType > 3) + return false; + + // "wrong type" "wrong length marker" + if ((signature[0] & 0xff) != 0x30 || (signature[1] & 0xff) != signature.length-3) + return false; + + int lenR = signature[3] & 0xff; + if (5 + lenR >= signature.length || lenR == 0) + return false; + int lenS = signature[5+lenR] & 0xff; + if (lenR + lenS + 7 != signature.length || lenS == 0) + return false; + + // R value type mismatch R value negative + if (signature[4-2] != 0x02 || (signature[4] & 0x80) == 0x80) + return false; + if (lenR > 1 && signature[4] == 0x00 && (signature[4+1] & 0x80) != 0x80) + return false; // R value excessively padded + + // S value type mismatch S value negative + if (signature[6 + lenR - 2] != 0x02 || (signature[6 + lenR] & 0x80) == 0x80) + return false; + if (lenS > 1 && signature[6 + lenR] == 0x00 && (signature[6 + lenR + 1] & 0x80) != 0x80) + return false; // S value excessively padded + + return true; + } + + + public static boolean VerifySign(byte[] tlvPublicKey, byte[] data, byte[] tlvSignature) throws IOException, SignatureException, InvalidKeyException, InvalidKeySpecException, NoSuchAlgorithmException, NoSuchProviderException { + Signature signature = Signature.getInstance("SHA256withECDSA"); + ECNamedCurveParameterSpec spec = ECNamedCurveTable.getParameterSpec("secp256k1"); + KeyFactory factory = KeyFactory.getInstance("EC", "SC"); + + ECPoint p1 = spec.getCurve().decodePoint(tlvPublicKey); + ECPublicKeySpec keySpec = new ECPublicKeySpec(p1, spec); + + PublicKey publicKey = factory.generatePublic(keySpec); + signature.initVerify(publicKey); + signature.update(data); + + ASN1EncodableVector v = new ASN1EncodableVector(); + int size = tlvSignature.length / 2; + v.add(/*r*/new ASN1Integer(new BigInteger(1, Arrays.copyOfRange(tlvSignature, 0, size)))); + v.add(/*s*/new ASN1Integer(new BigInteger(1, Arrays.copyOfRange(tlvSignature, size, size * 2)))); + byte[] sigDer = new DERSequence(v).getEncoded(); + + return signature.verify(sigDer); + } + + private static byte[] leaderZero(byte[] s) + { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + if (s[0] > 0x7f) { + baos.write((byte) 0x00); + } + for(int i = 0; i < 32; ++i) + baos.write(s[i]); + + return baos.toByteArray(); + } + public static boolean checkHashSign(byte[] pub, byte[] hash, byte[] sign) + { + byte[] rtmp = new byte[32]; + byte[] stmp = new byte[32]; + + for(int i = 0; i< 32; ++i) + { + rtmp[i] = sign[i]; + stmp[i] = sign[i+32]; + } + + leaderZero(rtmp); + byte[] r2 = leaderZero(rtmp); + byte[] s2 = leaderZero(stmp); + + BigInteger r = new BigInteger(r2); + BigInteger s = new BigInteger(s2); + ECDSASigner signer = new ECDSASigner(); + + ECPublicKeyParameters params = new ECPublicKeyParameters(CURVE.getCurve().decodePoint(pub), CURVE); + signer.init(false, params); + return signer.verifySignature(hash, r, s); + } + public static byte[] doubleSha256(byte[] bytes) { + try { + MessageDigest sha256 = MessageDigest.getInstance("SHA-256"); + return sha256.digest(sha256.digest(bytes)); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + } + + public static byte[] sha256ripemd160(byte[] publicKey) { + try { + MessageDigest sha256 = MessageDigest.getInstance("SHA-256"); + byte[] sha256hash = sha256.digest(publicKey); + byte[] hashedPublicKey = Util.calculateRIPEMD160(sha256hash); + return hashedPublicKey; + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } catch (NoSuchProviderException e) { + e.printStackTrace(); + } + return null; + } +} diff --git a/app/src/main/java/com/tangem/wallet/DerEncodingUtil.java b/app/src/main/java/com/tangem/wallet/DerEncodingUtil.java new file mode 100644 index 0000000000..8d0afcfb60 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/DerEncodingUtil.java @@ -0,0 +1,130 @@ +package com.tangem.wallet; + +import org.spongycastle.asn1.ASN1Integer; +import org.spongycastle.asn1.DERSequenceGenerator; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.math.BigInteger; + +/** + * Created by Ilia on 15.02.2018. + */ + +public class DerEncodingUtil { + + public static byte[] PackInteger(byte[] s) + { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + baos.write((byte)0x02); + + byte length = (byte)s.length; + if (s[0] > 0x7f) { + baos.write((byte)(length+1)); + baos.write((byte) 0x00); + } + else { + baos.write((byte)length); + } + + for(int i = 0; i < length; ++i) + baos.write(s[i]); + + return baos.toByteArray(); + } + + public static byte[] packSignDer(BigInteger r, BigInteger s, byte[] pubKey) throws IOException + { + byte[] signDer = DerEncoding(r, s); + BitcoinOutputStream packKey = new BitcoinOutputStream(); + + packKey.write((byte)0x41); + packKey.write(pubKey); + + byte[] keyArray = packKey.toByteArray(); + + BitcoinOutputStream result = new BitcoinOutputStream(); + result.write((byte)(signDer.length+1)); + result.write(signDer); + result.write((byte)0x1); + + result.write(keyArray); + + return result.toByteArray(); + + } + + public static byte[] packSignDerBitcoinCash(BigInteger r, BigInteger s, byte[] pubKey) throws IOException + { + byte[] signDer = DerEncoding(r, s); + BitcoinOutputStream packKey = new BitcoinOutputStream(); + + packKey.write((byte)0x21); //compress key + packKey.write(pubKey); + + byte[] keyArray = packKey.toByteArray(); + + BitcoinOutputStream result = new BitcoinOutputStream(); + result.write((byte)(signDer.length+1)); + result.write(signDer); + result.write((byte)0x41); + + result.write(keyArray); + + return result.toByteArray(); + + } + + + public static byte[] DerEncoding(BigInteger r, BigInteger s) throws IOException { + ByteArrayOutputStream bos = new ByteArrayOutputStream(72); + DERSequenceGenerator seq = new DERSequenceGenerator(bos); + seq.addObject(new ASN1Integer(r)); + seq.addObject(new ASN1Integer(s)); + seq.close(); + return bos.toByteArray(); + } + + public static byte[] DerEncoding(byte[] sign) + { + byte[] r = sign; + byte[] s = new byte[32]; + for(int i =0; i < 32; ++i) + { + s[i] = sign[i+32]; + } + + byte[] newR = PackInteger(r); + byte[] newS = PackInteger(s); + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + baos.write((byte)(newR.length+newS.length+2)); + baos.write((byte)newR.length); + baos.write(newR, 0, newR.length); + baos.write((byte)newS.length); + baos.write(newS, 0, newS.length); + + return baos.toByteArray(); + } + + public static byte[] DerEncodingBI(BigInteger[] sign) + { + byte[] r = sign[0].toByteArray(); + byte[] s = sign[1].toByteArray(); + + byte[] newR = PackInteger(r); + byte[] newS = PackInteger(s); + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + + baos.write((byte)(newR.length+newS.length+2)); + + baos.write((byte)newR.length); + baos.write(newR, 0, newR.length); + + baos.write((byte)newS.length); + baos.write(newS, 0, newS.length); + + return baos.toByteArray(); + } +} diff --git a/app/src/main/java/com/tangem/wallet/DeviceNFCAntennaLocation.java b/app/src/main/java/com/tangem/wallet/DeviceNFCAntennaLocation.java new file mode 100644 index 0000000000..f9279eb23f --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/DeviceNFCAntennaLocation.java @@ -0,0 +1,25 @@ +package com.tangem.wallet; + +import android.os.Build; + +public class DeviceNFCAntennaLocation { + + public float X; + public float Y; + public boolean OnBackSide; + public float Strength; + + public void getAntennaLocation() { + String device = DeviceName.getDeviceName(); + String model = Build.DEVICE; + this.X = 0.5f; + this.Y = 0.33f; + this.OnBackSide = true; + this.Strength = 1.0f; + // Samsung +// if (model.contains("Samsung")) {this.Y = 0.33; this.Strength = 0.5; } +// if (model.contains("Sony")) {this.Y = 0.33; this.Strength = 0.5; } +// if (device == "Galaxy J5") {this.Y = 0.4; this.Strength = 0.5; } + if (device == "P10 lite") {this.Y = 0.03f; this.Strength = 0.8f; } + } +} diff --git a/app/src/main/java/com/tangem/wallet/DeviceName.java b/app/src/main/java/com/tangem/wallet/DeviceName.java new file mode 100644 index 0000000000..ef1b1401b1 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/DeviceName.java @@ -0,0 +1,2091 @@ +package com.tangem.wallet; + +/** + * Created by Ilia on 20.04.2018. + */ +/* + * Copyright (C) 2017 Jared Rummler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + import android.Manifest; + import android.content.Context; + import android.content.SharedPreferences; + import android.content.pm.PackageManager; + import android.net.ConnectivityManager; + import android.net.NetworkInfo; + import android.os.Build; + import android.os.Handler; + import android.os.Looper; + import android.support.annotation.WorkerThread; + import android.text.TextUtils; + import java.io.BufferedReader; + import java.io.IOException; + import java.io.InputStreamReader; + import java.net.HttpURLConnection; + import java.net.URL; + import java.util.Locale; + import org.json.JSONArray; + import org.json.JSONException; + import org.json.JSONObject; + +// @formatter:off +/** + *

Get the consumer friendly name of an Android device.

+ * + *

On many popular devices the market name of the device is not available. For example, on the + * Samsung Galaxy S6 the value of {@link Build#MODEL} could be "SM-G920F", "SM-G920I", "SM-G920W8", + * etc.

+ * + *

See the usages below to get the consumer friends name of a device:

+ * + *

Get the name of the current device:

+ * + *
+ * String deviceName = DeviceName.getDeviceName();
+ * 
+ * + *

The above code will get the correct device name for the top 600 Android devices. If the + * device is unrecognized, then Build.MODEL is returned.

+ * + *

Get the name of a device using the device's codename:

+ * + *
+ * // Retruns "Moto X Style"
+ * DeviceName.getDeviceName("clark", "Unknown device");
+ * 
+ * + *

Get information about the device:

+ * + *
+ * DeviceName.with(context).request(new DeviceName.Callback() {
+ *
+ *   @Override public void onFinished(DeviceName.DeviceInfo info, Exception error) {
+ *     String manufacturer = info.manufacturer;  // "Samsung"
+ *     String name = info.marketName;            // "Galaxy S6 Edge"
+ *     String model = info.model;                // "SM-G925I"
+ *     String codename = info.codename;          // "zerolte"
+ *     String deviceName = info.getName();       // "Galaxy S6 Edge"
+ *     // FYI: We are on the UI thread.
+ *   }
+ * });
+ * 
+ * + *

The above code loads JSON from a generated list of device names based on Google's maintained + * list. It will be up-to-date with Google's supported device list so that you will get the correct + * name for new or unknown devices. This supports over 10,000 devices.

+ * + *

This will only make a network call once. The value is saved to SharedPreferences for future + * calls.

+ */ + +public class DeviceName { + + // @formatter:on + + // JSON which is derived from Google's PDF document which contains all devices on Google Play. + // To get the URL to the JSON file which contains information about the device name: + // String url = String.format(DEVICE_JSON_URL, Build.DEVICE); + private static final String DEVICE_JSON_URL = + "https://raw.githubusercontent.com/jaredrummler/AndroidDeviceNames/master/json/devices/%s.json"; + + // Preference filename for storing device info so we don't need to download it again. + private static final String SHARED_PREF_NAME = "device_names"; + + /** + * Create a new request to get information about a device. + * + * @param context + * the application context + * @return a new Request instance. + */ + public static Request with(Context context) { + return new Request(context.getApplicationContext()); + } + + /** + * Get the consumer friendly name of the device. + * + * @return the market name of the current device. + * @see #getDeviceName(String, String) + */ + public static String getDeviceName() { + return getDeviceName(Build.DEVICE, Build.MODEL, capitalize(Build.MODEL)); + } + + /** + * Get the consumer friendly name of a device. + * + * @param codename + * the value of the system property "ro.product.device" ({@link Build#DEVICE}) + * or + * the value of the system property "ro.product.model" ({@link Build#MODEL}) + * @param fallback + * the fallback name if the device is unknown. Usually the value of the system property + * "ro.product.model" ({@link Build#MODEL}) + * @return the market name of a device or {@code fallback} if the device is unknown. + */ + public static String getDeviceName(String codename, String fallback) { + return getDeviceName(codename, codename, fallback); + } + + /** + * Get the consumer friendly name of a device. + * + * @param codename + * the value of the system property "ro.product.device" ({@link Build#DEVICE}). + * @param model + * the value of the system property "ro.product.model" ({@link Build#MODEL}). + * @param fallback + * the fallback name if the device is unknown. Usually the value of the system property + * "ro.product.model" ({@link Build#MODEL}) + * @return the market name of a device or {@code fallback} if the device is unknown. + */ + public static String getDeviceName(String codename, String model, String fallback) { + // ---------------------------------------------------------------------------- + // Acer + if ((codename != null && codename.equals("acer_S57")) + || (model != null && model.equals("S57"))) { + return "Liquid Jade Z"; + } + if ((codename != null && codename.equals("acer_t08")) + || (model != null && model.equals("T08"))) { + return "Liquid Zest Plus"; + } + // ---------------------------------------------------------------------------- + // Asus + if ((codename != null && (codename.equals("grouper") + || codename.equals("tilapia")))) { + return "Nexus 7 (2012)"; + } + if ((codename != null && (codename.equals("deb") + || codename.equals("flo")))) { + return "Nexus 7 (2013)"; + } + // ---------------------------------------------------------------------------- + // Google + if ((codename != null && codename.equals("sailfish"))) { + return "Pixel"; + } + if ((codename != null && codename.equals("walleye"))) { + return "Pixel 2"; + } + if ((codename != null && codename.equals("taimen"))) { + return "Pixel 2 XL"; + } + if ((codename != null && codename.equals("dragon"))) { + return "Pixel C"; + } + if ((codename != null && codename.equals("marlin"))) { + return "Pixel XL"; + } + // ---------------------------------------------------------------------------- + // HTC + if ((codename != null && codename.equals("flounder"))) { + return "Nexus 9"; + } + // ---------------------------------------------------------------------------- + // Huawei + if ((codename != null && (codename.equals("HWBND-H"))) + || (model != null && (model.equals("BND-L21") + || model.equals("BND-L24")))) { + return "Honor 7X"; + } + if (model != null && model.contains("WAS-LX")) { + return "P10 lite"; + } + if ((codename != null && codename.equals("HWBKL")) + || (model != null && model.equals("BKL-L09"))) { + return "Honor View 10"; + } + if ((codename != null && (codename.equals("HWALP"))) + || (model != null && (model.equals("ALP-AL00") + || model.equals("ALP-L09") + || model.equals("ALP-L29") + || model.equals("ALP-TL00")))) { + return "Mate 10"; + } + if ((codename != null && (codename.equals("HWMHA"))) + || (model != null && (model.equals("MHA-AL00") + || model.equals("MHA-L09") + || model.equals("MHA-L29") + || model.equals("MHA-TL00")))) { + return "Mate 9"; + } + if ((codename != null && codename.equals("angler"))) { + return "Nexus 6P"; + } + // ---------------------------------------------------------------------------- + // LGE + if ((codename != null && (codename.equals("g2"))) + || (model != null && (model.equals("LG-D800") + || model.equals("LG-D801") + || model.equals("LG-D802") + || model.equals("LG-D802T") + || model.equals("LG-D802TR") + || model.equals("LG-D803") + || model.equals("LG-D805") + || model.equals("LG-D806") + || model.equals("LG-F320K") + || model.equals("LG-F320L") + || model.equals("LG-F320S") + || model.equals("LG-LS980") + || model.equals("VS980 4G")))) { + return "LG G2"; + } + if ((codename != null && (codename.equals("g3"))) + || (model != null && (model.equals("AS985") + || model.equals("LG-AS990") + || model.equals("LG-D850") + || model.equals("LG-D851") + || model.equals("LG-D852") + || model.equals("LG-D852G") + || model.equals("LG-D855") + || model.equals("LG-D856") + || model.equals("LG-D857") + || model.equals("LG-D858") + || model.equals("LG-D858HK") + || model.equals("LG-D859") + || model.equals("LG-F400K") + || model.equals("LG-F400L") + || model.equals("LG-F400S") + || model.equals("LGL24") + || model.equals("LGLS990") + || model.equals("LGUS990") + || model.equals("LGV31") + || model.equals("VS985 4G")))) { + return "LG G3"; + } + if ((codename != null && (codename.equals("p1"))) + || (model != null && (model.equals("AS986") + || model.equals("LG-AS811") + || model.equals("LG-AS991") + || model.equals("LG-F500K") + || model.equals("LG-F500L") + || model.equals("LG-F500S") + || model.equals("LG-H810") + || model.equals("LG-H811") + || model.equals("LG-H812") + || model.equals("LG-H815") + || model.equals("LG-H818") + || model.equals("LG-H819") + || model.equals("LGLS991") + || model.equals("LGUS991") + || model.equals("LGV32") + || model.equals("VS986")))) { + return "LG G4"; + } + if ((codename != null && (codename.equals("h1"))) + || (model != null && (model.equals("LG-F700K") + || model.equals("LG-F700L") + || model.equals("LG-F700S") + || model.equals("LG-H820") + || model.equals("LG-H820PR") + || model.equals("LG-H830") + || model.equals("LG-H831") + || model.equals("LG-H850") + || model.equals("LG-H858") + || model.equals("LG-H860") + || model.equals("LG-H868") + || model.equals("LGAS992") + || model.equals("LGLS992") + || model.equals("LGUS992") + || model.equals("RS988") + || model.equals("VS987")))) { + return "LG G5"; + } + if ((codename != null && (codename.equals("lucye"))) + || (model != null && (model.equals("LG-AS993") + || model.equals("LG-H870") + || model.equals("LG-H870AR") + || model.equals("LG-H870DS") + || model.equals("LG-H870I") + || model.equals("LG-H870S") + || model.equals("LG-H871") + || model.equals("LG-H872") + || model.equals("LG-H872PR") + || model.equals("LG-H873") + || model.equals("LG-LS993") + || model.equals("LGM-G600K") + || model.equals("LGM-G600L") + || model.equals("LGM-G600S") + || model.equals("LGUS997") + || model.equals("VS988")))) { + return "LG G6"; + } + if ((codename != null && codename.equals("mako"))) { + return "Nexus 4"; + } + if ((codename != null && codename.equals("hammerhead"))) { + return "Nexus 5"; + } + if ((codename != null && codename.equals("bullhead"))) { + return "Nexus 5X"; + } + // ---------------------------------------------------------------------------- + // Motorola + if ((codename != null && codename.equals("shamu"))) { + return "Nexus 6"; + } + // ---------------------------------------------------------------------------- + // OnePlus + if ((codename != null && codename.equals("OnePlus3")) + || (model != null && model.equals("ONEPLUS A3000"))) { + return "OnePlus3"; + } + if ((codename != null && codename.equals("OnePlus3T")) + || (model != null && model.equals("ONEPLUS A3000"))) { + return "OnePlus3T"; + } + if ((codename != null && codename.equals("OnePlus5")) + || (model != null && model.equals("ONEPLUS A5000"))) { + return "OnePlus5"; + } + if ((codename != null && codename.equals("OnePlus5T")) + || (model != null && model.equals("ONEPLUS A5010"))) { + return "OnePlus5T"; + } + // ---------------------------------------------------------------------------- + // Samsung + if ((codename != null && (codename.equals("a53g") + || codename.equals("a5lte") + || codename.equals("a5ltechn") + || codename.equals("a5ltectc") + || codename.equals("a5ltezh") + || codename.equals("a5ltezt") + || codename.equals("a5ulte") + || codename.equals("a5ultebmc") + || codename.equals("a5ultektt") + || codename.equals("a5ultelgt") + || codename.equals("a5ulteskt"))) + || (model != null && (model.equals("SM-A5000") + || model.equals("SM-A5009") + || model.equals("SM-A500F") + || model.equals("SM-A500F1") + || model.equals("SM-A500FU") + || model.equals("SM-A500G") + || model.equals("SM-A500H") + || model.equals("SM-A500K") + || model.equals("SM-A500L") + || model.equals("SM-A500M") + || model.equals("SM-A500S") + || model.equals("SM-A500W") + || model.equals("SM-A500X") + || model.equals("SM-A500XZ") + || model.equals("SM-A500Y") + || model.equals("SM-A500YZ")))) { + return "Galaxy A5"; + } + if ((codename != null && (codename.equals("vivaltods5m"))) + || (model != null && (model.equals("SM-G313HU") + || model.equals("SM-G313HY") + || model.equals("SM-G313M") + || model.equals("SM-G313MY")))) { + return "Galaxy Ace 4"; + } + if ((codename != null && (codename.equals("GT-S6352") + || codename.equals("GT-S6802") + || codename.equals("GT-S6802B") + || codename.equals("SCH-I579") + || codename.equals("SCH-I589") + || codename.equals("SCH-i579") + || codename.equals("SCH-i589"))) + || (model != null && (model.equals("GT-S6352") + || model.equals("GT-S6802") + || model.equals("GT-S6802B") + || model.equals("SCH-I589") + || model.equals("SCH-i579") + || model.equals("SCH-i589")))) { + return "Galaxy Ace Duos"; + } + if ((codename != null && (codename.equals("GT-S7500") + || codename.equals("GT-S7500L") + || codename.equals("GT-S7500T") + || codename.equals("GT-S7500W") + || codename.equals("GT-S7508"))) + || (model != null && (model.equals("GT-S7500") + || model.equals("GT-S7500L") + || model.equals("GT-S7500T") + || model.equals("GT-S7500W") + || model.equals("GT-S7508")))) { + return "Galaxy Ace Plus"; + } + if ((codename != null && (codename.equals("heat3gtfnvzw") + || codename.equals("heatnfc3g") + || codename.equals("heatqlte"))) + || (model != null && (model.equals("SM-G310HN") + || model.equals("SM-G357FZ") + || model.equals("SM-S765C") + || model.equals("SM-S766C")))) { + return "Galaxy Ace Style"; + } + if ((codename != null && (codename.equals("vivalto3g") + || codename.equals("vivalto3mve3g") + || codename.equals("vivalto5mve3g") + || codename.equals("vivaltolte") + || codename.equals("vivaltonfc3g"))) + || (model != null && (model.equals("SM-G313F") + || model.equals("SM-G313HN") + || model.equals("SM-G313ML") + || model.equals("SM-G313MU") + || model.equals("SM-G316H") + || model.equals("SM-G316HU") + || model.equals("SM-G316M") + || model.equals("SM-G316MY")))) { + return "Galaxy Ace4"; + } + if ((codename != null && (codename.equals("core33g") + || codename.equals("coreprimelte") + || codename.equals("coreprimelteaio") + || codename.equals("coreprimeltelra") + || codename.equals("coreprimeltespr") + || codename.equals("coreprimeltetfnvzw") + || codename.equals("coreprimeltevzw") + || codename.equals("coreprimeve3g") + || codename.equals("coreprimevelte") + || codename.equals("cprimeltemtr") + || codename.equals("cprimeltetmo") + || codename.equals("rossalte") + || codename.equals("rossaltectc") + || codename.equals("rossaltexsa"))) + || (model != null && (model.equals("SAMSUNG-SM-G360AZ") + || model.equals("SM-G3606") + || model.equals("SM-G3608") + || model.equals("SM-G3609") + || model.equals("SM-G360F") + || model.equals("SM-G360FY") + || model.equals("SM-G360GY") + || model.equals("SM-G360H") + || model.equals("SM-G360HU") + || model.equals("SM-G360M") + || model.equals("SM-G360P") + || model.equals("SM-G360R6") + || model.equals("SM-G360T") + || model.equals("SM-G360T1") + || model.equals("SM-G360V") + || model.equals("SM-G361F") + || model.equals("SM-G361H") + || model.equals("SM-G361HU") + || model.equals("SM-G361M") + || model.equals("SM-S820L")))) { + return "Galaxy Core Prime"; + } + if ((codename != null && (codename.equals("kanas") + || codename.equals("kanas3g") + || codename.equals("kanas3gcmcc") + || codename.equals("kanas3gctc") + || codename.equals("kanas3gnfc"))) + || (model != null && (model.equals("SM-G3556D") + || model.equals("SM-G3558") + || model.equals("SM-G3559") + || model.equals("SM-G355H") + || model.equals("SM-G355HN") + || model.equals("SM-G355HQ") + || model.equals("SM-G355M")))) { + return "Galaxy Core2"; + } + if ((codename != null && (codename.equals("e53g") + || codename.equals("e5lte") + || codename.equals("e5ltetfnvzw") + || codename.equals("e5ltetw"))) + || (model != null && (model.equals("SM-E500F") + || model.equals("SM-E500H") + || model.equals("SM-E500M") + || model.equals("SM-E500YZ") + || model.equals("SM-S978L")))) { + return "Galaxy E5"; + } + if ((codename != null && (codename.equals("e73g") + || codename.equals("e7lte") + || codename.equals("e7ltechn") + || codename.equals("e7ltectc") + || codename.equals("e7ltehktw"))) + || (model != null && (model.equals("SM-E7000") + || model.equals("SM-E7009") + || model.equals("SM-E700F") + || model.equals("SM-E700H") + || model.equals("SM-E700M")))) { + return "Galaxy E7"; + } + if ((codename != null && (codename.equals("SCH-I629") + || codename.equals("nevis") + || codename.equals("nevis3g") + || codename.equals("nevis3gcmcc") + || codename.equals("nevisds") + || codename.equals("nevisnvess") + || codename.equals("nevisp") + || codename.equals("nevisvess") + || codename.equals("nevisw"))) + || (model != null && (model.equals("GT-S6790") + || model.equals("GT-S6790E") + || model.equals("GT-S6790L") + || model.equals("GT-S6790N") + || model.equals("GT-S6810") + || model.equals("GT-S6810B") + || model.equals("GT-S6810E") + || model.equals("GT-S6810L") + || model.equals("GT-S6810M") + || model.equals("GT-S6810P") + || model.equals("GT-S6812") + || model.equals("GT-S6812B") + || model.equals("GT-S6812C") + || model.equals("GT-S6812i") + || model.equals("GT-S6818") + || model.equals("GT-S6818V") + || model.equals("SCH-I629")))) { + return "Galaxy Fame"; + } + if ((codename != null && codename.equals("grandprimelteatt")) + || (model != null && model.equals("SAMSUNG-SM-G530A"))) { + return "Galaxy Go Prime"; + } + if ((codename != null && (codename.equals("baffinlite") + || codename.equals("baffinlitedtv") + || codename.equals("baffinq3g"))) + || (model != null && (model.equals("GT-I9060") + || model.equals("GT-I9060L") + || model.equals("GT-I9063T") + || model.equals("GT-I9082C") + || model.equals("GT-I9168") + || model.equals("GT-I9168I")))) { + return "Galaxy Grand Neo"; + } + if ((codename != null && (codename.equals("fortuna3g") + || codename.equals("fortuna3gdtv") + || codename.equals("fortunalte") + || codename.equals("fortunaltectc") + || codename.equals("fortunaltezh") + || codename.equals("fortunaltezt") + || codename.equals("fortunave3g") + || codename.equals("gprimelteacg") + || codename.equals("gprimeltecan") + || codename.equals("gprimeltemtr") + || codename.equals("gprimeltespr") + || codename.equals("gprimeltetfnvzw") + || codename.equals("gprimeltetmo") + || codename.equals("gprimelteusc") + || codename.equals("grandprimelte") + || codename.equals("grandprimelteaio") + || codename.equals("grandprimeve3g") + || codename.equals("grandprimeve3gdtv") + || codename.equals("grandprimevelte") + || codename.equals("grandprimevelteltn") + || codename.equals("grandprimeveltezt"))) + || (model != null && (model.equals("SAMSUNG-SM-G530AZ") + || model.equals("SM-G5306W") + || model.equals("SM-G5308W") + || model.equals("SM-G5309W") + || model.equals("SM-G530BT") + || model.equals("SM-G530F") + || model.equals("SM-G530FZ") + || model.equals("SM-G530H") + || model.equals("SM-G530M") + || model.equals("SM-G530MU") + || model.equals("SM-G530P") + || model.equals("SM-G530R4") + || model.equals("SM-G530R7") + || model.equals("SM-G530T") + || model.equals("SM-G530T1") + || model.equals("SM-G530W") + || model.equals("SM-G530Y") + || model.equals("SM-G531BT") + || model.equals("SM-G531F") + || model.equals("SM-G531H") + || model.equals("SM-G531M") + || model.equals("SM-G531Y") + || model.equals("SM-S920L") + || model.equals("gprimelteacg")))) { + return "Galaxy Grand Prime"; + } + if ((codename != null && (codename.equals("ms013g") + || codename.equals("ms013gdtv") + || codename.equals("ms013gss") + || codename.equals("ms01lte") + || codename.equals("ms01ltektt") + || codename.equals("ms01ltelgt") + || codename.equals("ms01lteskt"))) + || (model != null && (model.equals("SM-G710") + || model.equals("SM-G7102") + || model.equals("SM-G7102T") + || model.equals("SM-G7105") + || model.equals("SM-G7105H") + || model.equals("SM-G7105L") + || model.equals("SM-G7106") + || model.equals("SM-G7108") + || model.equals("SM-G7109") + || model.equals("SM-G710K") + || model.equals("SM-G710L") + || model.equals("SM-G710S")))) { + return "Galaxy Grand2"; + } + if ((codename != null && (codename.equals("j13g") + || codename.equals("j13gtfnvzw") + || codename.equals("j1lte") + || codename.equals("j1nlte") + || codename.equals("j1qltevzw") + || codename.equals("j1xlte") + || codename.equals("j1xlteaio") + || codename.equals("j1xlteatt") + || codename.equals("j1xltecan") + || codename.equals("j1xqltespr") + || codename.equals("j1xqltetfnvzw"))) + || (model != null && (model.equals("SAMSUNG-SM-J120A") + || model.equals("SAMSUNG-SM-J120AZ") + || model.equals("SM-J100F") + || model.equals("SM-J100FN") + || model.equals("SM-J100G") + || model.equals("SM-J100H") + || model.equals("SM-J100M") + || model.equals("SM-J100ML") + || model.equals("SM-J100MU") + || model.equals("SM-J100VPP") + || model.equals("SM-J100Y") + || model.equals("SM-J120F") + || model.equals("SM-J120FN") + || model.equals("SM-J120M") + || model.equals("SM-J120P") + || model.equals("SM-J120W") + || model.equals("SM-S120VL") + || model.equals("SM-S777C")))) { + return "Galaxy J1"; + } + if ((codename != null && (codename.equals("j1acelte") + || codename.equals("j1acelteltn") + || codename.equals("j1acevelte") + || codename.equals("j1pop3g"))) + || (model != null && (model.equals("SM-J110F") + || model.equals("SM-J110G") + || model.equals("SM-J110H") + || model.equals("SM-J110L") + || model.equals("SM-J110M") + || model.equals("SM-J111F") + || model.equals("SM-J111M")))) { + return "Galaxy J1 Ace"; + } + if ((codename != null && (codename.equals("j53g") + || codename.equals("j5lte") + || codename.equals("j5ltechn") + || codename.equals("j5ltekx") + || codename.equals("j5nlte") + || codename.equals("j5ylte"))) + || (model != null && (model.equals("SM-J5007") + || model.equals("SM-J5008") + || model.equals("SM-J500F") + || model.equals("SM-J500FN") + || model.equals("SM-J500G") + || model.equals("SM-J500H") + || model.equals("SM-J500M") + || model.equals("SM-J500N0") + || model.equals("SM-J500Y")))) { + return "Galaxy J5"; + } + if ((codename != null && (codename.equals("j75ltektt") + || codename.equals("j7e3g") + || codename.equals("j7elte") + || codename.equals("j7ltechn"))) + || (model != null && (model.equals("SM-J7008") + || model.equals("SM-J700F") + || model.equals("SM-J700H") + || model.equals("SM-J700K") + || model.equals("SM-J700M")))) { + return "Galaxy J7"; + } + if ((codename != null && (codename.equals("maguro") + || codename.equals("toro") + || codename.equals("toroplus"))) + || (model != null && (model.equals("Galaxy X")))) { + return "Galaxy Nexus"; + } + if ((codename != null && (codename.equals("lt033g") + || codename.equals("lt03ltektt") + || codename.equals("lt03ltelgt") + || codename.equals("lt03lteskt") + || codename.equals("p4notelte") + || codename.equals("p4noteltektt") + || codename.equals("p4noteltelgt") + || codename.equals("p4notelteskt") + || codename.equals("p4noteltespr") + || codename.equals("p4notelteusc") + || codename.equals("p4noteltevzw") + || codename.equals("p4noterf") + || codename.equals("p4noterfktt") + || codename.equals("p4notewifi") + || codename.equals("p4notewifi43241any") + || codename.equals("p4notewifiany") + || codename.equals("p4notewifiktt") + || codename.equals("p4notewifiww"))) + || (model != null && (model.equals("GT-N8000") + || model.equals("GT-N8005") + || model.equals("GT-N8010") + || model.equals("GT-N8013") + || model.equals("GT-N8020") + || model.equals("SCH-I925") + || model.equals("SCH-I925U") + || model.equals("SHV-E230K") + || model.equals("SHV-E230L") + || model.equals("SHV-E230S") + || model.equals("SHW-M480K") + || model.equals("SHW-M480W") + || model.equals("SHW-M485W") + || model.equals("SHW-M486W") + || model.equals("SM-P601") + || model.equals("SM-P602") + || model.equals("SM-P605K") + || model.equals("SM-P605L") + || model.equals("SM-P605S") + || model.equals("SPH-P600")))) { + return "Galaxy Note 10.1"; + } + if ((codename != null && (codename.equals("SC-01G") + || codename.equals("SCL24") + || codename.equals("tbeltektt") + || codename.equals("tbeltelgt") + || codename.equals("tbelteskt") + || codename.equals("tblte") + || codename.equals("tblteatt") + || codename.equals("tbltecan") + || codename.equals("tbltechn") + || codename.equals("tbltespr") + || codename.equals("tbltetmo") + || codename.equals("tblteusc") + || codename.equals("tbltevzw"))) + || (model != null && (model.equals("SAMSUNG-SM-N915A") + || model.equals("SC-01G") + || model.equals("SCL24") + || model.equals("SM-N9150") + || model.equals("SM-N915F") + || model.equals("SM-N915FY") + || model.equals("SM-N915G") + || model.equals("SM-N915K") + || model.equals("SM-N915L") + || model.equals("SM-N915P") + || model.equals("SM-N915R4") + || model.equals("SM-N915S") + || model.equals("SM-N915T") + || model.equals("SM-N915T3") + || model.equals("SM-N915V") + || model.equals("SM-N915W8") + || model.equals("SM-N915X")))) { + return "Galaxy Note Edge"; + } + if ((codename != null && (codename.equals("v1a3g") + || codename.equals("v1awifi") + || codename.equals("v1awifikx") + || codename.equals("viennalte") + || codename.equals("viennalteatt") + || codename.equals("viennaltekx") + || codename.equals("viennaltevzw"))) + || (model != null && (model.equals("SAMSUNG-SM-P907A") + || model.equals("SM-P900") + || model.equals("SM-P901") + || model.equals("SM-P905") + || model.equals("SM-P905F0") + || model.equals("SM-P905M") + || model.equals("SM-P905V")))) { + return "Galaxy Note Pro 12.2"; + } + if ((codename != null && (codename.equals("tre3caltektt") + || codename.equals("tre3caltelgt") + || codename.equals("tre3calteskt") + || codename.equals("tre3g") + || codename.equals("trelte") + || codename.equals("treltektt") + || codename.equals("treltelgt") + || codename.equals("trelteskt") + || codename.equals("trhplte") + || codename.equals("trlte") + || codename.equals("trlteatt") + || codename.equals("trltecan") + || codename.equals("trltechn") + || codename.equals("trltechnzh") + || codename.equals("trltespr") + || codename.equals("trltetmo") + || codename.equals("trlteusc") + || codename.equals("trltevzw"))) + || (model != null && (model.equals("SAMSUNG-SM-N910A") + || model.equals("SM-N9100") + || model.equals("SM-N9106W") + || model.equals("SM-N9108V") + || model.equals("SM-N9109W") + || model.equals("SM-N910C") + || model.equals("SM-N910F") + || model.equals("SM-N910G") + || model.equals("SM-N910H") + || model.equals("SM-N910K") + || model.equals("SM-N910L") + || model.equals("SM-N910P") + || model.equals("SM-N910R4") + || model.equals("SM-N910S") + || model.equals("SM-N910T") + || model.equals("SM-N910T2") + || model.equals("SM-N910T3") + || model.equals("SM-N910U") + || model.equals("SM-N910V") + || model.equals("SM-N910W8") + || model.equals("SM-N910X") + || model.equals("SM-N916K") + || model.equals("SM-N916L") + || model.equals("SM-N916S")))) { + return "Galaxy Note4"; + } + if ((codename != null && (codename.equals("noblelte") + || codename.equals("noblelteacg") + || codename.equals("noblelteatt") + || codename.equals("nobleltebmc") + || codename.equals("nobleltechn") + || codename.equals("nobleltecmcc") + || codename.equals("nobleltehk") + || codename.equals("nobleltektt") + || codename.equals("nobleltelgt") + || codename.equals("nobleltelra") + || codename.equals("noblelteskt") + || codename.equals("nobleltespr") + || codename.equals("nobleltetmo") + || codename.equals("noblelteusc") + || codename.equals("nobleltevzw"))) + || (model != null && (model.equals("SAMSUNG-SM-N920A") + || model.equals("SM-N9200") + || model.equals("SM-N9208") + || model.equals("SM-N920C") + || model.equals("SM-N920F") + || model.equals("SM-N920G") + || model.equals("SM-N920I") + || model.equals("SM-N920K") + || model.equals("SM-N920L") + || model.equals("SM-N920P") + || model.equals("SM-N920R4") + || model.equals("SM-N920R6") + || model.equals("SM-N920R7") + || model.equals("SM-N920S") + || model.equals("SM-N920T") + || model.equals("SM-N920V") + || model.equals("SM-N920W8") + || model.equals("SM-N920X")))) { + return "Galaxy Note5"; + } + if ((codename != null && (codename.equals("SC-01J") + || codename.equals("SCV34") + || codename.equals("gracelte") + || codename.equals("graceltektt") + || codename.equals("graceltelgt") + || codename.equals("gracelteskt") + || codename.equals("graceqlteacg") + || codename.equals("graceqlteatt") + || codename.equals("graceqltebmc") + || codename.equals("graceqltechn") + || codename.equals("graceqltedcm") + || codename.equals("graceqltelra") + || codename.equals("graceqltespr") + || codename.equals("graceqltetfnvzw") + || codename.equals("graceqltetmo") + || codename.equals("graceqlteue") + || codename.equals("graceqlteusc") + || codename.equals("graceqltevzw"))) + || (model != null && (model.equals("SAMSUNG-SM-N930A") + || model.equals("SC-01J") + || model.equals("SCV34") + || model.equals("SGH-N037") + || model.equals("SM-N9300") + || model.equals("SM-N930F") + || model.equals("SM-N930K") + || model.equals("SM-N930L") + || model.equals("SM-N930P") + || model.equals("SM-N930R4") + || model.equals("SM-N930R6") + || model.equals("SM-N930R7") + || model.equals("SM-N930S") + || model.equals("SM-N930T") + || model.equals("SM-N930U") + || model.equals("SM-N930V") + || model.equals("SM-N930VL") + || model.equals("SM-N930W8") + || model.equals("SM-N930X")))) { + return "Galaxy Note7"; + } + if ((codename != null && (codename.equals("SC-01K") + || codename.equals("SCV37") + || codename.equals("greatlte") + || codename.equals("greatlteks") + || codename.equals("greatqlte") + || codename.equals("greatqltechn") + || codename.equals("greatqltecmcc") + || codename.equals("greatqltecs") + || codename.equals("greatqlteue"))) + || (model != null && (model.equals("SC-01K") + || model.equals("SCV37") + || model.equals("SM-N9500") + || model.equals("SM-N9508") + || model.equals("SM-N950F") + || model.equals("SM-N950N") + || model.equals("SM-N950U") + || model.equals("SM-N950U1") + || model.equals("SM-N950W") + || model.equals("SM-N950XN")))) { + return "Galaxy Note8"; + } + if ((codename != null && (codename.equals("o5lte") + || codename.equals("o5ltechn") + || codename.equals("o5prolte") + || codename.equals("on5ltemtr") + || codename.equals("on5ltetfntmo") + || codename.equals("on5ltetmo"))) + || (model != null && (model.equals("SM-G5500") + || model.equals("SM-G550FY") + || model.equals("SM-G550T") + || model.equals("SM-G550T1") + || model.equals("SM-G550T2") + || model.equals("SM-S550TL")))) { + return "Galaxy On5"; + } + if ((codename != null && (codename.equals("o7lte") + || codename.equals("o7ltechn") + || codename.equals("on7elte"))) + || (model != null && (model.equals("SM-G6000") + || model.equals("SM-G600F") + || model.equals("SM-G600FY")))) { + return "Galaxy On7"; + } + if ((codename != null && (codename.equals("GT-I9000") + || codename.equals("GT-I9000B") + || codename.equals("GT-I9000M") + || codename.equals("GT-I9000T") + || codename.equals("GT-I9003") + || codename.equals("GT-I9003L") + || codename.equals("GT-I9008L") + || codename.equals("GT-I9010") + || codename.equals("GT-I9018") + || codename.equals("GT-I9050") + || codename.equals("SC-02B") + || codename.equals("SCH-I500") + || codename.equals("SCH-S950C") + || codename.equals("SCH-i909") + || codename.equals("SGH-I897") + || codename.equals("SGH-T959V") + || codename.equals("SGH-T959W") + || codename.equals("SHW-M110S") + || codename.equals("SHW-M190S") + || codename.equals("SPH-D700") + || codename.equals("loganlte"))) + || (model != null && (model.equals("GT-I9000") + || model.equals("GT-I9000B") + || model.equals("GT-I9000M") + || model.equals("GT-I9000T") + || model.equals("GT-I9003") + || model.equals("GT-I9003L") + || model.equals("GT-I9008L") + || model.equals("GT-I9010") + || model.equals("GT-I9018") + || model.equals("GT-I9050") + || model.equals("GT-S7275") + || model.equals("SAMSUNG-SGH-I897") + || model.equals("SC-02B") + || model.equals("SCH-I500") + || model.equals("SCH-S950C") + || model.equals("SCH-i909") + || model.equals("SGH-T959V") + || model.equals("SGH-T959W") + || model.equals("SHW-M110S") + || model.equals("SHW-M190S") + || model.equals("SPH-D700")))) { + return "Galaxy S"; + } + if ((codename != null && (codename.equals("kylechn") + || codename.equals("kyleopen") + || codename.equals("kyletdcmcc"))) + || (model != null && (model.equals("GT-S7562") + || model.equals("GT-S7568")))) { + return "Galaxy S Duos"; + } + if ((codename != null && (codename.equals("kyleprods"))) + || (model != null && (model.equals("GT-S7582") + || model.equals("GT-S7582L")))) { + return "Galaxy S Duos2"; + } + if ((codename != null && codename.equals("vivalto3gvn")) + || (model != null && model.equals("SM-G313HZ"))) { + return "Galaxy S Duos3"; + } + if ((codename != null && (codename.equals("SC-03E") + || codename.equals("c1att") + || codename.equals("c1ktt") + || codename.equals("c1lgt") + || codename.equals("c1skt") + || codename.equals("d2att") + || codename.equals("d2can") + || codename.equals("d2cri") + || codename.equals("d2dcm") + || codename.equals("d2lteMetroPCS") + || codename.equals("d2lterefreshspr") + || codename.equals("d2ltetmo") + || codename.equals("d2mtr") + || codename.equals("d2spi") + || codename.equals("d2spr") + || codename.equals("d2tfnspr") + || codename.equals("d2tfnvzw") + || codename.equals("d2tmo") + || codename.equals("d2usc") + || codename.equals("d2vmu") + || codename.equals("d2vzw") + || codename.equals("d2xar") + || codename.equals("m0") + || codename.equals("m0apt") + || codename.equals("m0chn") + || codename.equals("m0cmcc") + || codename.equals("m0ctc") + || codename.equals("m0ctcduos") + || codename.equals("m0skt") + || codename.equals("m3") + || codename.equals("m3dcm"))) + || (model != null && (model.equals("GT-I9300") + || model.equals("GT-I9300T") + || model.equals("GT-I9305") + || model.equals("GT-I9305N") + || model.equals("GT-I9305T") + || model.equals("GT-I9308") + || model.equals("Gravity") + || model.equals("GravityQuad") + || model.equals("SAMSUNG-SGH-I747") + || model.equals("SC-03E") + || model.equals("SC-06D") + || model.equals("SCH-I535") + || model.equals("SCH-I535PP") + || model.equals("SCH-I939") + || model.equals("SCH-I939D") + || model.equals("SCH-L710") + || model.equals("SCH-R530C") + || model.equals("SCH-R530M") + || model.equals("SCH-R530U") + || model.equals("SCH-R530X") + || model.equals("SCH-S960L") + || model.equals("SCH-S968C") + || model.equals("SGH-I747M") + || model.equals("SGH-I748") + || model.equals("SGH-T999") + || model.equals("SGH-T999L") + || model.equals("SGH-T999N") + || model.equals("SGH-T999V") + || model.equals("SHV-E210K") + || model.equals("SHV-E210L") + || model.equals("SHV-E210S") + || model.equals("SHW-M440S") + || model.equals("SPH-L710") + || model.equals("SPH-L710T")))) { + return "Galaxy S3"; + } + if ((codename != null && (codename.equals("golden") + || codename.equals("goldenlteatt") + || codename.equals("goldenltebmc") + || codename.equals("goldenltevzw") + || codename.equals("goldenve3g"))) + || (model != null && (model.equals("GT-I8190") + || model.equals("GT-I8190L") + || model.equals("GT-I8190N") + || model.equals("GT-I8190T") + || model.equals("GT-I8200L") + || model.equals("SAMSUNG-SM-G730A") + || model.equals("SM-G730V") + || model.equals("SM-G730W8")))) { + return "Galaxy S3 Mini"; + } + if ((codename != null && (codename.equals("goldenve3g") + || codename.equals("goldenvess3g"))) + || (model != null && (model.equals("GT-I8200") + || model.equals("GT-I8200N") + || model.equals("GT-I8200Q")))) { + return "Galaxy S3 Mini Value Edition"; + } + if ((codename != null && (codename.equals("s3ve3g") + || codename.equals("s3ve3gdd") + || codename.equals("s3ve3gds") + || codename.equals("s3ve3gdsdd"))) + || (model != null && (model.equals("GT-I9300I") + || model.equals("GT-I9301I") + || model.equals("GT-I9301Q")))) { + return "Galaxy S3 Neo"; + } + if ((codename != null && (codename.equals("SC-04E") + || codename.equals("ja3g") + || codename.equals("ja3gduosctc") + || codename.equals("jaltektt") + || codename.equals("jaltelgt") + || codename.equals("jalteskt") + || codename.equals("jflte") + || codename.equals("jflteMetroPCS") + || codename.equals("jflteaio") + || codename.equals("jflteatt") + || codename.equals("jfltecan") + || codename.equals("jfltecri") + || codename.equals("jfltecsp") + || codename.equals("jfltelra") + || codename.equals("jflterefreshspr") + || codename.equals("jfltespr") + || codename.equals("jfltetfnatt") + || codename.equals("jfltetfntmo") + || codename.equals("jfltetmo") + || codename.equals("jflteusc") + || codename.equals("jfltevzw") + || codename.equals("jfltevzwpp") + || codename.equals("jftdd") + || codename.equals("jfvelte") + || codename.equals("jfwifi") + || codename.equals("jsglte") + || codename.equals("ks01lte") + || codename.equals("ks01ltektt") + || codename.equals("ks01ltelgt"))) + || (model != null && (model.equals("GT-I9500") + || model.equals("GT-I9505") + || model.equals("GT-I9505X") + || model.equals("GT-I9506") + || model.equals("GT-I9507") + || model.equals("GT-I9507V") + || model.equals("GT-I9508") + || model.equals("GT-I9508C") + || model.equals("GT-I9508V") + || model.equals("GT-I9515") + || model.equals("GT-I9515L") + || model.equals("SAMSUNG-SGH-I337") + || model.equals("SAMSUNG-SGH-I337Z") + || model.equals("SC-04E") + || model.equals("SCH-I545") + || model.equals("SCH-I545L") + || model.equals("SCH-I545PP") + || model.equals("SCH-I959") + || model.equals("SCH-R970") + || model.equals("SCH-R970C") + || model.equals("SCH-R970X") + || model.equals("SGH-I337M") + || model.equals("SGH-M919") + || model.equals("SGH-M919N") + || model.equals("SGH-M919V") + || model.equals("SGH-S970G") + || model.equals("SHV-E300K") + || model.equals("SHV-E300L") + || model.equals("SHV-E300S") + || model.equals("SHV-E330K") + || model.equals("SHV-E330L") + || model.equals("SM-S975L") + || model.equals("SPH-L720") + || model.equals("SPH-L720T")))) { + return "Galaxy S4"; + } + if ((codename != null && (codename.equals("serrano3g") + || codename.equals("serranods") + || codename.equals("serranolte") + || codename.equals("serranoltebmc") + || codename.equals("serranoltektt") + || codename.equals("serranoltekx") + || codename.equals("serranoltelra") + || codename.equals("serranoltespr") + || codename.equals("serranolteusc") + || codename.equals("serranoltevzw") + || codename.equals("serranove3g") + || codename.equals("serranovelte") + || codename.equals("serranovolteatt"))) + || (model != null && (model.equals("GT-I9190") + || model.equals("GT-I9192") + || model.equals("GT-I9192I") + || model.equals("GT-I9195") + || model.equals("GT-I9195I") + || model.equals("GT-I9195L") + || model.equals("GT-I9195T") + || model.equals("GT-I9195X") + || model.equals("GT-I9197") + || model.equals("SAMSUNG-SGH-I257") + || model.equals("SCH-I435") + || model.equals("SCH-I435L") + || model.equals("SCH-R890") + || model.equals("SGH-I257M") + || model.equals("SHV-E370D") + || model.equals("SHV-E370K") + || model.equals("SPH-L520")))) { + return "Galaxy S4 Mini"; + } + if ((codename != null && (codename.equals("SC-04F") + || codename.equals("SCL23") + || codename.equals("k3g") + || codename.equals("klte") + || codename.equals("klteMetroPCS") + || codename.equals("klteacg") + || codename.equals("klteaio") + || codename.equals("klteatt") + || codename.equals("kltecan") + || codename.equals("klteduoszn") + || codename.equals("kltektt") + || codename.equals("kltelgt") + || codename.equals("kltelra") + || codename.equals("klteskt") + || codename.equals("kltespr") + || codename.equals("kltetfnvzw") + || codename.equals("kltetmo") + || codename.equals("klteusc") + || codename.equals("kltevzw") + || codename.equals("kwifi") + || codename.equals("lentisltektt") + || codename.equals("lentisltelgt") + || codename.equals("lentislteskt"))) + || (model != null && (model.equals("SAMSUNG-SM-G900A") + || model.equals("SAMSUNG-SM-G900AZ") + || model.equals("SC-04F") + || model.equals("SCL23") + || model.equals("SM-G9006W") + || model.equals("SM-G9008W") + || model.equals("SM-G9009W") + || model.equals("SM-G900F") + || model.equals("SM-G900FQ") + || model.equals("SM-G900H") + || model.equals("SM-G900I") + || model.equals("SM-G900K") + || model.equals("SM-G900L") + || model.equals("SM-G900M") + || model.equals("SM-G900MD") + || model.equals("SM-G900P") + || model.equals("SM-G900R4") + || model.equals("SM-G900R6") + || model.equals("SM-G900R7") + || model.equals("SM-G900S") + || model.equals("SM-G900T") + || model.equals("SM-G900T1") + || model.equals("SM-G900T3") + || model.equals("SM-G900T4") + || model.equals("SM-G900V") + || model.equals("SM-G900W8") + || model.equals("SM-G900X") + || model.equals("SM-G906K") + || model.equals("SM-G906L") + || model.equals("SM-G906S") + || model.equals("SM-S903VL")))) { + return "Galaxy S5"; + } + if ((codename != null && (codename.equals("s5neolte") + || codename.equals("s5neoltecan"))) + || (model != null && (model.equals("SM-G903F") + || model.equals("SM-G903M") + || model.equals("SM-G903W")))) { + return "Galaxy S5 Neo"; + } + if ((codename != null && (codename.equals("SC-05G") + || codename.equals("zeroflte") + || codename.equals("zeroflteacg") + || codename.equals("zeroflteaio") + || codename.equals("zeroflteatt") + || codename.equals("zerofltebmc") + || codename.equals("zerofltechn") + || codename.equals("zerofltectc") + || codename.equals("zerofltektt") + || codename.equals("zerofltelgt") + || codename.equals("zerofltelra") + || codename.equals("zerofltemtr") + || codename.equals("zeroflteskt") + || codename.equals("zerofltespr") + || codename.equals("zerofltetfnvzw") + || codename.equals("zerofltetmo") + || codename.equals("zeroflteusc") + || codename.equals("zerofltevzw"))) + || (model != null && (model.equals("SAMSUNG-SM-G920A") + || model.equals("SAMSUNG-SM-G920AZ") + || model.equals("SC-05G") + || model.equals("SM-G9200") + || model.equals("SM-G9208") + || model.equals("SM-G9209") + || model.equals("SM-G920F") + || model.equals("SM-G920I") + || model.equals("SM-G920K") + || model.equals("SM-G920L") + || model.equals("SM-G920P") + || model.equals("SM-G920R4") + || model.equals("SM-G920R6") + || model.equals("SM-G920R7") + || model.equals("SM-G920S") + || model.equals("SM-G920T") + || model.equals("SM-G920T1") + || model.equals("SM-G920V") + || model.equals("SM-G920W8") + || model.equals("SM-G920X") + || model.equals("SM-S906L") + || model.equals("SM-S907VL")))) { + return "Galaxy S6"; + } + if ((codename != null && (codename.equals("404SC") + || codename.equals("SC-04G") + || codename.equals("SCV31") + || codename.equals("zerolte") + || codename.equals("zerolteacg") + || codename.equals("zerolteatt") + || codename.equals("zeroltebmc") + || codename.equals("zeroltechn") + || codename.equals("zeroltektt") + || codename.equals("zeroltelgt") + || codename.equals("zeroltelra") + || codename.equals("zerolteskt") + || codename.equals("zeroltespr") + || codename.equals("zeroltetmo") + || codename.equals("zerolteusc") + || codename.equals("zeroltevzw"))) + || (model != null && (model.equals("404SC") + || model.equals("SAMSUNG-SM-G925A") + || model.equals("SC-04G") + || model.equals("SCV31") + || model.equals("SM-G9250") + || model.equals("SM-G925F") + || model.equals("SM-G925I") + || model.equals("SM-G925K") + || model.equals("SM-G925L") + || model.equals("SM-G925P") + || model.equals("SM-G925R4") + || model.equals("SM-G925R6") + || model.equals("SM-G925R7") + || model.equals("SM-G925S") + || model.equals("SM-G925T") + || model.equals("SM-G925V") + || model.equals("SM-G925W8") + || model.equals("SM-G925X")))) { + return "Galaxy S6 Edge"; + } + if ((codename != null && (codename.equals("zenlte") + || codename.equals("zenlteatt") + || codename.equals("zenltebmc") + || codename.equals("zenltechn") + || codename.equals("zenltektt") + || codename.equals("zenltekx") + || codename.equals("zenltelgt") + || codename.equals("zenlteskt") + || codename.equals("zenltespr") + || codename.equals("zenltetmo") + || codename.equals("zenlteusc") + || codename.equals("zenltevzw"))) + || (model != null && (model.equals("SAMSUNG-SM-G928A") + || model.equals("SM-G9280") + || model.equals("SM-G9287") + || model.equals("SM-G9287C") + || model.equals("SM-G928C") + || model.equals("SM-G928F") + || model.equals("SM-G928G") + || model.equals("SM-G928I") + || model.equals("SM-G928K") + || model.equals("SM-G928L") + || model.equals("SM-G928N0") + || model.equals("SM-G928P") + || model.equals("SM-G928R4") + || model.equals("SM-G928S") + || model.equals("SM-G928T") + || model.equals("SM-G928V") + || model.equals("SM-G928W8") + || model.equals("SM-G928X")))) { + return "Galaxy S6 Edge+"; + } + if ((codename != null && (codename.equals("herolte") + || codename.equals("heroltebmc") + || codename.equals("heroltektt") + || codename.equals("heroltelgt") + || codename.equals("herolteskt") + || codename.equals("heroqlteacg") + || codename.equals("heroqlteaio") + || codename.equals("heroqlteatt") + || codename.equals("heroqltecctvzw") + || codename.equals("heroqltechn") + || codename.equals("heroqltelra") + || codename.equals("heroqltemtr") + || codename.equals("heroqltespr") + || codename.equals("heroqltetfnvzw") + || codename.equals("heroqltetmo") + || codename.equals("heroqlteue") + || codename.equals("heroqlteusc") + || codename.equals("heroqltevzw"))) + || (model != null && (model.equals("SAMSUNG-SM-G930A") + || model.equals("SAMSUNG-SM-G930AZ") + || model.equals("SM-G9300") + || model.equals("SM-G9308") + || model.equals("SM-G930F") + || model.equals("SM-G930K") + || model.equals("SM-G930L") + || model.equals("SM-G930P") + || model.equals("SM-G930R4") + || model.equals("SM-G930R6") + || model.equals("SM-G930R7") + || model.equals("SM-G930S") + || model.equals("SM-G930T") + || model.equals("SM-G930T1") + || model.equals("SM-G930U") + || model.equals("SM-G930V") + || model.equals("SM-G930VC") + || model.equals("SM-G930VL") + || model.equals("SM-G930W8") + || model.equals("SM-G930X")))) { + return "Galaxy S7"; + } + if ((codename != null && (codename.equals("SC-02H") + || codename.equals("SCV33") + || codename.equals("hero2lte") + || codename.equals("hero2ltebmc") + || codename.equals("hero2ltektt") + || codename.equals("hero2ltelgt") + || codename.equals("hero2lteskt") + || codename.equals("hero2qlteatt") + || codename.equals("hero2qltecctvzw") + || codename.equals("hero2qltechn") + || codename.equals("hero2qltespr") + || codename.equals("hero2qltetmo") + || codename.equals("hero2qlteue") + || codename.equals("hero2qlteusc") + || codename.equals("hero2qltevzw"))) + || (model != null && (model.equals("SAMSUNG-SM-G935A") + || model.equals("SC-02H") + || model.equals("SCV33") + || model.equals("SM-G9350") + || model.equals("SM-G935F") + || model.equals("SM-G935K") + || model.equals("SM-G935L") + || model.equals("SM-G935P") + || model.equals("SM-G935R4") + || model.equals("SM-G935S") + || model.equals("SM-G935T") + || model.equals("SM-G935U") + || model.equals("SM-G935V") + || model.equals("SM-G935VC") + || model.equals("SM-G935W8") + || model.equals("SM-G935X")))) { + return "Galaxy S7 Edge"; + } + if ((codename != null && (codename.equals("SC-02J") + || codename.equals("SCV36") + || codename.equals("dreamlte") + || codename.equals("dreamlteks") + || codename.equals("dreamqltecan") + || codename.equals("dreamqltechn") + || codename.equals("dreamqltecmcc") + || codename.equals("dreamqltesq") + || codename.equals("dreamqlteue"))) + || (model != null && (model.equals("SC-02J") + || model.equals("SCV36") + || model.equals("SM-G9500") + || model.equals("SM-G9508") + || model.equals("SM-G950F") + || model.equals("SM-G950N") + || model.equals("SM-G950U") + || model.equals("SM-G950U1") + || model.equals("SM-G950W")))) { + return "Galaxy S8"; + } + if ((codename != null && (codename.equals("SC-03J") + || codename.equals("SCV35") + || codename.equals("dream2lte") + || codename.equals("dream2lteks") + || codename.equals("dream2qltecan") + || codename.equals("dream2qltechn") + || codename.equals("dream2qltesq") + || codename.equals("dream2qlteue"))) + || (model != null && (model.equals("SC-03J") + || model.equals("SCV35") + || model.equals("SM-G9550") + || model.equals("SM-G955F") + || model.equals("SM-G955N") + || model.equals("SM-G955U") + || model.equals("SM-G955U1") + || model.equals("SM-G955W")))) { + return "Galaxy S8+"; + } + if ((codename != null && (codename.equals("starlte") + || codename.equals("starlteks") + || codename.equals("starqltechn") + || codename.equals("starqltecmcc") + || codename.equals("starqltecs") + || codename.equals("starqltesq") + || codename.equals("starqlteue"))) + || (model != null && (model.equals("SM-G9600") + || model.equals("SM-G9608") + || model.equals("SM-G960F") + || model.equals("SM-G960N") + || model.equals("SM-G960U") + || model.equals("SM-G960U1") + || model.equals("SM-G960W")))) { + return "Galaxy S9"; + } + if ((codename != null && (codename.equals("star2lte") + || codename.equals("star2lteks") + || codename.equals("star2qltechn") + || codename.equals("star2qltecs") + || codename.equals("star2qltesq") + || codename.equals("star2qlteue"))) + || (model != null && (model.equals("SM-G9650") + || model.equals("SM-G965F") + || model.equals("SM-G965N") + || model.equals("SM-G965U") + || model.equals("SM-G965U1") + || model.equals("SM-G965W")))) { + return "Galaxy S9+"; + } + if ((codename != null && (codename.equals("GT-P7500") + || codename.equals("GT-P7500D") + || codename.equals("GT-P7503") + || codename.equals("GT-P7510") + || codename.equals("SC-01D") + || codename.equals("SCH-I905") + || codename.equals("SGH-T859") + || codename.equals("SHW-M300W") + || codename.equals("SHW-M380K") + || codename.equals("SHW-M380S") + || codename.equals("SHW-M380W"))) + || (model != null && (model.equals("GT-P7500") + || model.equals("GT-P7500D") + || model.equals("GT-P7503") + || model.equals("GT-P7510") + || model.equals("SC-01D") + || model.equals("SCH-I905") + || model.equals("SGH-T859") + || model.equals("SHW-M300W") + || model.equals("SHW-M380K") + || model.equals("SHW-M380S") + || model.equals("SHW-M380W")))) { + return "Galaxy Tab 10.1"; + } + if ((codename != null && (codename.equals("GT-P6200") + || codename.equals("GT-P6200L") + || codename.equals("GT-P6201") + || codename.equals("GT-P6210") + || codename.equals("GT-P6211") + || codename.equals("SC-02D") + || codename.equals("SGH-T869") + || codename.equals("SHW-M430W"))) + || (model != null && (model.equals("GT-P6200") + || model.equals("GT-P6200L") + || model.equals("GT-P6201") + || model.equals("GT-P6210") + || model.equals("GT-P6211") + || model.equals("SC-02D") + || model.equals("SGH-T869") + || model.equals("SHW-M430W")))) { + return "Galaxy Tab 7.0 Plus"; + } + if ((codename != null && (codename.equals("gteslteatt") + || codename.equals("gtesltebmc") + || codename.equals("gtesltelgt") + || codename.equals("gteslteskt") + || codename.equals("gtesltetmo") + || codename.equals("gtesltetw") + || codename.equals("gtesltevzw") + || codename.equals("gtesqltespr") + || codename.equals("gtesqlteusc"))) + || (model != null && (model.equals("SAMSUNG-SM-T377A") + || model.equals("SM-T375L") + || model.equals("SM-T375S") + || model.equals("SM-T3777") + || model.equals("SM-T377P") + || model.equals("SM-T377R4") + || model.equals("SM-T377T") + || model.equals("SM-T377V") + || model.equals("SM-T377W")))) { + return "Galaxy Tab E 8.0"; + } + if ((codename != null && (codename.equals("gtel3g") + || codename.equals("gtelltevzw") + || codename.equals("gtelwifi") + || codename.equals("gtelwifichn") + || codename.equals("gtelwifiue"))) + || (model != null && (model.equals("SM-T560") + || model.equals("SM-T560NU") + || model.equals("SM-T561") + || model.equals("SM-T561M") + || model.equals("SM-T561Y") + || model.equals("SM-T562") + || model.equals("SM-T567V")))) { + return "Galaxy Tab E 9.6"; + } + if ((codename != null && (codename.equals("403SC") + || codename.equals("degas2wifi") + || codename.equals("degas2wifibmwchn") + || codename.equals("degas3g") + || codename.equals("degaslte") + || codename.equals("degasltespr") + || codename.equals("degasltevzw") + || codename.equals("degasvelte") + || codename.equals("degasveltechn") + || codename.equals("degaswifi") + || codename.equals("degaswifibmwzc") + || codename.equals("degaswifidtv") + || codename.equals("degaswifiopenbnn") + || codename.equals("degaswifiue"))) + || (model != null && (model.equals("403SC") + || model.equals("SM-T230") + || model.equals("SM-T230NT") + || model.equals("SM-T230NU") + || model.equals("SM-T230NW") + || model.equals("SM-T230NY") + || model.equals("SM-T230X") + || model.equals("SM-T231") + || model.equals("SM-T232") + || model.equals("SM-T235") + || model.equals("SM-T235Y") + || model.equals("SM-T237P") + || model.equals("SM-T237V") + || model.equals("SM-T239") + || model.equals("SM-T2397") + || model.equals("SM-T239C") + || model.equals("SM-T239M")))) { + return "Galaxy Tab4 7.0"; + } + if ((codename != null && (codename.equals("gvlte") + || codename.equals("gvlteatt") + || codename.equals("gvltevzw") + || codename.equals("gvltexsp") + || codename.equals("gvwifijpn") + || codename.equals("gvwifiue"))) + || (model != null && (model.equals("SAMSUNG-SM-T677A") + || model.equals("SM-T670") + || model.equals("SM-T677") + || model.equals("SM-T677V")))) { + return "Galaxy View"; + } + if ((codename != null && codename.equals("manta"))) { + return "Nexus 10"; + } + // ---------------------------------------------------------------------------- + // Sony + if ((codename != null && (codename.equals("D2104") + || codename.equals("D2105"))) + || (model != null && (model.equals("D2104") + || model.equals("D2105")))) { + return "Xperia E1 dual"; + } + if ((codename != null && (codename.equals("D2202") + || codename.equals("D2203") + || codename.equals("D2206") + || codename.equals("D2243"))) + || (model != null && (model.equals("D2202") + || model.equals("D2203") + || model.equals("D2206") + || model.equals("D2243")))) { + return "Xperia E3"; + } + if ((codename != null && (codename.equals("E5603") + || codename.equals("E5606") + || codename.equals("E5653"))) + || (model != null && (model.equals("E5603") + || model.equals("E5606") + || model.equals("E5653")))) { + return "Xperia M5"; + } + if ((codename != null && (codename.equals("E5633") + || codename.equals("E5643") + || codename.equals("E5663"))) + || (model != null && (model.equals("E5633") + || model.equals("E5643") + || model.equals("E5663")))) { + return "Xperia M5 Dual"; + } + if ((codename != null && codename.equals("LT26i")) + || (model != null && model.equals("LT26i"))) { + return "Xperia S"; + } + if ((codename != null && (codename.equals("D5303") + || codename.equals("D5306") + || codename.equals("D5316") + || codename.equals("D5316N") + || codename.equals("D5322"))) + || (model != null && (model.equals("D5303") + || model.equals("D5306") + || model.equals("D5316") + || model.equals("D5316N") + || model.equals("D5322")))) { + return "Xperia T2 Ultra"; + } + if ((codename != null && (codename.equals("txs03"))) + || (model != null && (model.equals("SGPT12") + || model.equals("SGPT13")))) { + return "Xperia Tablet S"; + } + if ((codename != null && (codename.equals("SGP311") + || codename.equals("SGP312") + || codename.equals("SGP321") + || codename.equals("SGP351"))) + || (model != null && (model.equals("SGP311") + || model.equals("SGP312") + || model.equals("SGP321") + || model.equals("SGP351")))) { + return "Xperia Tablet Z"; + } + if ((codename != null && (codename.equals("D6502") + || codename.equals("D6503") + || codename.equals("D6543") + || codename.equals("SO-03F"))) + || (model != null && (model.equals("D6502") + || model.equals("D6503") + || model.equals("D6543") + || model.equals("SO-03F")))) { + return "Xperia Z2"; + } + if ((codename != null && (codename.equals("401SO") + || codename.equals("D6603") + || codename.equals("D6616") + || codename.equals("D6643") + || codename.equals("D6646") + || codename.equals("D6653") + || codename.equals("SO-01G") + || codename.equals("SOL26") + || codename.equals("leo"))) + || (model != null && (model.equals("401SO") + || model.equals("D6603") + || model.equals("D6616") + || model.equals("D6643") + || model.equals("D6646") + || model.equals("D6653") + || model.equals("SO-01G") + || model.equals("SOL26")))) { + return "Xperia Z3"; + } + if ((codename != null && (codename.equals("402SO") + || codename.equals("SO-03G") + || codename.equals("SOV31"))) + || (model != null && (model.equals("402SO") + || model.equals("SO-03G") + || model.equals("SOV31")))) { + return "Xperia Z4"; + } + if ((codename != null && (codename.equals("E5803") + || codename.equals("E5823") + || codename.equals("SO-02H"))) + || (model != null && (model.equals("E5803") + || model.equals("E5823") + || model.equals("SO-02H")))) { + return "Xperia Z5 Compact"; + } + // ---------------------------------------------------------------------------- + // Sony Ericsson + if ((codename != null && (codename.equals("LT26i") + || codename.equals("SO-02D"))) + || (model != null && (model.equals("LT26i") + || model.equals("SO-02D")))) { + return "Xperia S"; + } + if ((codename != null && (codename.equals("SGP311") + || codename.equals("SGP321") + || codename.equals("SGP341") + || codename.equals("SO-03E"))) + || (model != null && (model.equals("SGP311") + || model.equals("SGP321") + || model.equals("SGP341") + || model.equals("SO-03E")))) { + return "Xperia Tablet Z"; + } + return fallback; + } + + /** + * Get the {@link DeviceInfo} for the current device. Do not run on the UI thread, as this may + * download JSON to retrieve the {@link DeviceInfo}. JSON is only downloaded once and then + * stored to {@link SharedPreferences}. + * + * @param context + * the application context. + * @return {@link DeviceInfo} for the current device. + */ + @WorkerThread + public static DeviceInfo getDeviceInfo(Context context) { + return getDeviceInfo(context.getApplicationContext(), Build.DEVICE, Build.MODEL); + } + + /** + * Get the {@link DeviceInfo} for the current device. Do not run on the UI thread, as this may + * download JSON to retrieve the {@link DeviceInfo}. JSON is only downloaded once and then + * stored to {@link SharedPreferences}. + * + * @param context + * the application context. + * @param codename + * the codename of the device + * @return {@link DeviceInfo} for the current device. + */ + @WorkerThread + public static DeviceInfo getDeviceInfo(Context context, String codename) { + return getDeviceInfo(context, codename, null); + } + + /** + * Get the {@link DeviceInfo} for the current device. Do not run on the UI thread, as this may + * download JSON to retrieve the {@link DeviceInfo}. JSON is only downloaded once and then + * stored to {@link SharedPreferences}. + * + * @param context + * the application context. + * @param codename + * the codename of the device + * @param model + * the model of the device + * @return {@link DeviceInfo} for the current device. + */ + @WorkerThread + public static DeviceInfo getDeviceInfo(Context context, String codename, String model) { + SharedPreferences prefs = context.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE); + String key = String.format("%s:%s", codename, model); + String savedJson = prefs.getString(key, null); + if (savedJson != null) { + try { + return new DeviceInfo(new JSONObject(savedJson)); + } catch (JSONException e) { + e.printStackTrace(); + } + } + + // check if we have an internet connection + int ret = context.checkCallingOrSelfPermission(Manifest.permission.ACCESS_NETWORK_STATE); + boolean isConnectedToNetwork = false; + if (ret == PackageManager.PERMISSION_GRANTED) { + ConnectivityManager connMgr = (ConnectivityManager) + context.getSystemService(Context.CONNECTIVITY_SERVICE); + NetworkInfo networkInfo = connMgr.getActiveNetworkInfo(); + if (networkInfo != null && networkInfo.isConnected()) { + isConnectedToNetwork = true; + } + } else { + // assume we are connected. + isConnectedToNetwork = true; + } + + if (isConnectedToNetwork) { + try { + // Get the device name from the generated JSON files created from Google's device list. + String url = String.format(DEVICE_JSON_URL, codename.toLowerCase(Locale.ENGLISH)); + String jsonString = downloadJson(url); + JSONArray jsonArray = new JSONArray(jsonString); + for (int i = 0, len = jsonArray.length(); i < len; i++) { + JSONObject json = jsonArray.getJSONObject(i); + DeviceInfo info = new DeviceInfo(json); + if ((codename.equalsIgnoreCase(info.codename) && model == null) + || codename.equalsIgnoreCase(info.codename) && model.equalsIgnoreCase(info.model)) { + // Save to SharedPreferences so we don't need to make another request. + SharedPreferences.Editor editor = prefs.edit(); + editor.putString(key, json.toString()); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) { + editor.apply(); + } else { + editor.commit(); + } + return info; + } + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + if (codename.equals(Build.DEVICE) && model.equals(Build.MODEL)) { + return new DeviceInfo(Build.MANUFACTURER, getDeviceName(), codename, model); // current device + } + + return new DeviceInfo(null, null, codename, model); // unknown device + } + + /** + *

Capitalizes getAllProcesses the whitespace separated words in a String. Only the first + * letter of each word is changed.

+ * + * Whitespace is defined by {@link Character#isWhitespace(char)}. + * + * @param str + * the String to capitalize + * @return capitalized The capitalized String + */ + private static String capitalize(String str) { + if (TextUtils.isEmpty(str)) { + return str; + } + char[] arr = str.toCharArray(); + boolean capitalizeNext = true; + String phrase = ""; + for (char c : arr) { + if (capitalizeNext && Character.isLetter(c)) { + phrase += Character.toUpperCase(c); + capitalizeNext = false; + continue; + } else if (Character.isWhitespace(c)) { + capitalizeNext = true; + } + phrase += c; + } + return phrase; + } + + /** Download URL to String */ + @WorkerThread + private static String downloadJson(String myurl) throws IOException { + StringBuilder sb = new StringBuilder(); + BufferedReader reader = null; + try { + URL url = new URL(myurl); + HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + conn.setReadTimeout(10000); + conn.setConnectTimeout(15000); + conn.setRequestMethod("GET"); + conn.setDoInput(true); + conn.connect(); + if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) { + reader = new BufferedReader(new InputStreamReader(conn.getInputStream())); + String line; + while ((line = reader.readLine()) != null) { + sb.append(line).append('\n'); + } + } + return sb.toString(); + } finally { + if (reader != null) { + reader.close(); + } + } + } + + public static final class Request { + + final Context context; + final Handler handler; + String codename; + String model; + + private Request(Context ctx) { + context = ctx; + handler = new Handler(ctx.getMainLooper()); + } + + /** + * Set the device codename to query. You should also set the model. + * + * @param codename + * the value of the system property "ro.product.device" + * @return This Request object to allow for chaining of calls to set methods. + * @see Build#DEVICE + */ + public Request setCodename(String codename) { + this.codename = codename; + return this; + } + + /** + * Set the device model to query. You should also set the codename. + * + * @param model + * the value of the system property "ro.product.model" + * @return This Request object to allow for chaining of calls to set methods. + * @see Build#MODEL + */ + public Request setModel(String model) { + this.model = model; + return this; + } + + /** + * Download information about the device. This saves the results in shared-preferences so + * future requests will not need a network connection. + * + * @param callback + * the callback to retrieve the {@link DeviceInfo} + */ + public void request(Callback callback) { + if (codename == null && model == null) { + codename = Build.DEVICE; + model = Build.MODEL; + } + GetDeviceRunnable runnable = new GetDeviceRunnable(callback); + if (Looper.myLooper() == Looper.getMainLooper()) { + new Thread(runnable).start(); + } else { + runnable.run(); // already running in background thread. + } + } + + private final class GetDeviceRunnable implements Runnable { + + final Callback callback; + DeviceInfo deviceInfo; + Exception error; + + public GetDeviceRunnable(Callback callback) { + this.callback = callback; + } + + @Override public void run() { + try { + deviceInfo = getDeviceInfo(context, codename, model); + } catch (Exception e) { + error = e; + } + handler.post(new Runnable() { + + @Override public void run() { + callback.onFinished(deviceInfo, error); + } + }); + } + } + + } + + /** + * Callback which is invoked when the {@link DeviceInfo} is finished loading. + */ + public interface Callback { + + /** + * Callback to get the device info. This is run on the UI thread. + * + * @param info + * the requested {@link DeviceInfo} + * @param error + * {@code null} if nothing went wrong. + */ + void onFinished(DeviceInfo info, Exception error); + } + + /** + * Device information based on + * Google's maintained list. + */ + public static final class DeviceInfo { + + /** Retail branding */ + public final String manufacturer; + + /** Marketing name */ + public final String marketName; + + /** the value of the system property "ro.product.device" */ + public final String codename; + + /** the value of the system property "ro.product.model" */ + public final String model; + + public DeviceInfo(String manufacturer, String marketName, String codename, String model) { + this.manufacturer = manufacturer; + this.marketName = marketName; + this.codename = codename; + this.model = model; + } + + private DeviceInfo(JSONObject jsonObject) throws JSONException { + manufacturer = jsonObject.getString("manufacturer"); + marketName = jsonObject.getString("market_name"); + codename = jsonObject.getString("codename"); + model = jsonObject.getString("model"); + } + + /** + * @return the consumer friendly name of the device. + */ + public String getName() { + if (!TextUtils.isEmpty(marketName)) { + return marketName; + } + return capitalize(model); + } + } + +} + diff --git a/app/src/main/java/com/tangem/wallet/Digest.java b/app/src/main/java/com/tangem/wallet/Digest.java new file mode 100644 index 0000000000..b3924d61c7 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/Digest.java @@ -0,0 +1,114 @@ +package com.tangem.wallet; + +/** + * Created by Ilia on 18.12.2017. + */ + +public interface Digest{ + + /** + * Insert one more input data byte. + * + * @param in the input byte + */ + void update(byte in); + + /** + * Insert some more bytes. + * + * @param inbuf the data bytes + */ + void update(byte[] inbuf); + + /** + * Insert some more bytes. + * + * @param inbuf the data buffer + * @param off the data offset in {@code inbuf} + * @param len the data length (in bytes) + */ + void update(byte[] inbuf, int off, int len); + + /** + * Finalize the current hash computation and return the hash value + * in a newly-allocated array. The object is resetted. + * + * @return the hash output + */ + byte[] digest(); + + /** + * Input some bytes, then finalize the current hash computation + * and return the hash value in a newly-allocated array. The object + * is resetted. + * + * @param inbuf the input data + * @return the hash output + */ + byte[] digest(byte[] inbuf); + + /** + * Finalize the current hash computation and store the hash value + * in the provided output buffer. The {@code len} parameter + * contains the maximum number of bytes that should be written; + * no more bytes than the natural hash function output length will + * be produced. If {@code len} is smaller than the natural + * hash output length, the hash output is truncated to its first + * {@code len} bytes. The object is resetted. + * + * @param outbuf the output buffer + * @param off the output offset within {@code outbuf} + * @param len the requested hash output length (in bytes) + * @return the number of bytes actually written in {@code outbuf} + */ + int digest(byte[] outbuf, int off, int len); + + /** + * Get the natural hash function output length (in bytes). + * + * @return the digest output length (in bytes) + */ + int getDigestLength(); + + /** + * Reset the object: this makes it suitable for a new hash + * computation. The current computation, if any, is discarded. + */ + void reset(); + + /** + * Clone the current state. The returned object evolves independantly + * of this object. + * + * @return the clone + */ + Digest copy(); + + /** + *

Return the "block length" for the hash function. This + * value is naturally defined for iterated hash functions + * (Merkle-Damgard). It is used in HMAC (that's what the + * HMAC specification + * names the "{@code B}" parameter).

+ * + *

If the function is "block-less" then this function may + * return {@code -n} where {@code n} is an integer such that the + * block length for HMAC ("{@code B}") will be inferred from the + * key length, by selecting the smallest multiple of {@code n} + * which is no smaller than the key length. For instance, for + * the Fugue-xxx hash functions, this function returns -4: the + * virtual block length B is the HMAC key length, rounded up to + * the next multiple of 4.

+ * + * @return the internal block length (in bytes), or {@code -n} + */ + int getBlockLength(); + + /** + *

Get the display name for this function (e.g. {@code "SHA-1"} + * for SHA-1).

+ * + * @see Object + */ + String toString(); +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/wallet/DigestEngine.java b/app/src/main/java/com/tangem/wallet/DigestEngine.java new file mode 100644 index 0000000000..8dd130353d --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/DigestEngine.java @@ -0,0 +1,220 @@ +package com.tangem.wallet; + + +import java.security.MessageDigest; + +/** + * Created by Ilia on 18.12.2017. + */ + +public abstract class DigestEngine extends MessageDigest implements Digest { + + /** + * Reset the hash algorithm state. + */ + protected abstract void engineReset(); + + /** + * Process one block of data. + * + * @param data the data block + */ + protected abstract void processBlock(byte[] data); + + /** + * Perform the final padding and store the result in the + * provided buffer. This method shall call {@link #flush} + * and then {@link #update} with the appropriate padding + * data in order to get the full input data. + * + * @param buf the output buffer + * @param off the output offset + */ + protected abstract void doPadding(byte[] buf, int off); + + /** + * This function is called at object creation time; the + * implementation should use it to perform initialization tasks. + * After this method is called, the implementation should be ready + * to process data or meaningfully honour calls such as + * {@link #engineGetDigestLength} + */ + protected abstract void doInit(); + + private int digestLen, blockLen, inputLen; + private byte[] inputBuf, outputBuf; + private long blockCount; + + /** + * Instantiate the engine. + */ + public DigestEngine(String alg) + { + super(alg); + doInit(); + digestLen = engineGetDigestLength(); + blockLen = getInternalBlockLength(); + inputBuf = new byte[blockLen]; + outputBuf = new byte[digestLen]; + inputLen = 0; + blockCount = 0; + } + + private void adjustDigestLen() + { + if (digestLen == 0) { + digestLen = engineGetDigestLength(); + outputBuf = new byte[digestLen]; + } + } + + public byte[] digest() + { + adjustDigestLen(); + byte[] result = new byte[digestLen]; + digest(result, 0, digestLen); + return result; + } + + public byte[] digest(byte[] input) + { + update(input, 0, input.length); + return digest(); + } + + public int digest(byte[] buf, int offset, int len) + { + adjustDigestLen(); + if (len >= digestLen) { + doPadding(buf, offset); + reset(); + return digestLen; + } else { + doPadding(outputBuf, 0); + System.arraycopy(outputBuf, 0, buf, offset, len); + reset(); + return len; + } + } + + public void reset() + { + engineReset(); + inputLen = 0; + blockCount = 0; + } + + public void update(byte input) + { + inputBuf[inputLen ++] = (byte)input; + if (inputLen == blockLen) { + processBlock(inputBuf); + blockCount ++; + inputLen = 0; + } + } + + public void update(byte[] input) + { + update(input, 0, input.length); + } + + public void update(byte[] input, int offset, int len) + { + while (len > 0) { + int copyLen = blockLen - inputLen; + if (copyLen > len) + copyLen = len; + System.arraycopy(input, offset, inputBuf, inputLen, + copyLen); + offset += copyLen; + inputLen += copyLen; + len -= copyLen; + if (inputLen == blockLen) { + processBlock(inputBuf); + blockCount ++; + inputLen = 0; + } + } + } + + /** + * Get the internal block length. This is the length (in + * bytes) of the array which will be passed as parameter to + * {@link #processBlock}. The default implementation of this + * method calls {@link #getBlockLength} and returns the same + * value. Overriding this method is useful when the advertised + * block length (which is used, for instance, by HMAC) is + * suboptimal with regards to internal buffering needs. + * + * @return the internal block length (in bytes) + */ + protected int getInternalBlockLength() + { + return getBlockLength(); + } + + /** + * Flush internal buffers, so that less than a block of data + * may at most be upheld. + * + * @return the number of bytes still unprocessed after the flush + */ + protected final int flush() + { + return inputLen; + } + + /** + * Get a reference to an internal buffer with the same size + * than a block. The contents of that buffer are defined only + * immediately after a call to {@link #flush()}: if + * {@link #flush()} return the value {@code n}, then the + * first {@code n} bytes of the array returned by this method + * are the {@code n} bytes of input data which are still + * unprocessed. The values of the remaining bytes are + * undefined and may be altered at will. + * + * @return a block-sized internal buffer + */ + protected final byte[] getBlockBuffer() + { + return inputBuf; + } + + /** + * Get the "block count": this is the number of times the + * {@link #processBlock} method has been invoked for the + * current hash operation. That counter is incremented + * after the call to {@link #processBlock}. + * + * @return the block count + */ + protected long getBlockCount() + { + return blockCount; + } + + /** + * This function copies the internal buffering state to some + * other instance of a class extending {@code DigestEngine}. + * It returns a reference to the copy. This method is intended + * to be called by the implementation of the {@link #copy} + * method. + * + * @param dest the copy + * @return the value {@code dest} + */ + protected Digest copyState(DigestEngine dest) + { + dest.inputLen = inputLen; + dest.blockCount = blockCount; + System.arraycopy(inputBuf, 0, dest.inputBuf, 0, + inputBuf.length); + adjustDigestLen(); + dest.adjustDigestLen(); + System.arraycopy(outputBuf, 0, dest.outputBuf, 0, + outputBuf.length); + return dest; + } +} diff --git a/app/src/main/java/com/tangem/wallet/ECDSASignature_ETH.java b/app/src/main/java/com/tangem/wallet/ECDSASignature_ETH.java new file mode 100644 index 0000000000..98d2f3e47f --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/ECDSASignature_ETH.java @@ -0,0 +1,52 @@ +package com.tangem.wallet; + +import java.math.BigInteger; + +/** + * Created by Ilia on 07.01.2018. + */ + +public class ECDSASignature_ETH { + /** + * The two components of the signature. + */ + public final BigInteger r, s; + public byte v; + + /** + * Constructs a signature with the given components. Does NOT automatically canonicalise the signature. + * + * @param r - + * @param s - + */ + public ECDSASignature_ETH(BigInteger r, BigInteger s) { + this.r = r; + this.s = s; + } + + /** + *t + * @param r + * @param s + * @return - + */ + private static ECDSASignature_ETH fromComponents(byte[] r, byte[] s) { + return new ECDSASignature_ETH(new BigInteger(1, r), new BigInteger(1, s)); + } + + /** + * + * @param r - + * @param s - + * @param v - + * @return - + */ + public static ECDSASignature_ETH fromComponents(byte[] r, byte[] s, byte v) { + ECDSASignature_ETH signature = fromComponents(r, s); + signature.v = v; + return signature; + } + + +} + diff --git a/app/src/main/java/com/tangem/wallet/ETH_Transaction.java b/app/src/main/java/com/tangem/wallet/ETH_Transaction.java new file mode 100644 index 0000000000..476bc096ba --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/ETH_Transaction.java @@ -0,0 +1,229 @@ +package com.tangem.wallet; + + +import android.util.Log; + +import org.bitcoinj.core.ECKey; +import org.bitcoinj.core.Sha256Hash; +import org.spongycastle.util.BigIntegers; +import org.spongycastle.util.encoders.Hex; + +import java.math.BigInteger; +import java.util.Arrays; + +import static com.tangem.wallet.ByteUtil.EMPTY_BYTE_ARRAY; + +/** + * Created by Ilia on 07.01.2018. + */ + +public class ETH_Transaction { + byte[] nonce; + byte[] gasPrice; + byte[] gasLimit; + byte[] receiveAddress; + byte[] value; + byte[] data; + Integer chainId; + byte[] rlpRaw; + public ECDSASignature_ETH signature; + byte[] rlpEncoded; + + private static final int CHAIN_ID_INC = 35; + private static final int LOWER_REAL_V = 27; + + public static ETH_Transaction create(String to, BigInteger amount, BigInteger nonce, BigInteger gasPrice, + BigInteger gasLimit, Integer chainId){ + return new ETH_Transaction(BigIntegers.asUnsignedByteArray(nonce), + BigIntegers.asUnsignedByteArray(gasPrice), + BigIntegers.asUnsignedByteArray(gasLimit), + Hex.decode(to), + BigIntegers.asUnsignedByteArray(amount), + null, + chainId); + } + + + public static ETH_Transaction create(String to, BigInteger amount, BigInteger nonce, BigInteger gasPrice, + BigInteger gasLimit, Integer chainId, byte[] data){ + return new ETH_Transaction(BigIntegers.asUnsignedByteArray(nonce), + BigIntegers.asUnsignedByteArray(gasPrice), + BigIntegers.asUnsignedByteArray(gasLimit), + Hex.decode(to), + BigIntegers.asUnsignedByteArray(amount), + data, + chainId); + } + + public ETH_Transaction(byte[] nonce, byte[] gasPrice, byte[] gasLimit, byte[] receiveAddress, byte[] value, byte[] data, + Integer chainId) { + this.nonce = nonce; + this.gasPrice = gasPrice; + this.gasLimit = gasLimit; + this.receiveAddress = receiveAddress; + if (ByteUtil.isSingleZero(value)) { + this.value = EMPTY_BYTE_ARRAY; + } else { + this.value = value; + } + this.data = data; + this.chainId = chainId; + + if (receiveAddress == null) { + this.receiveAddress = ByteUtil.EMPTY_BYTE_ARRAY; + } + } + + public enum ChainEnum + { + Mainnet(1), + Morden(2), + Ropsten(3), + Rinkeby(4), + Rootstock_mainnet(30), + Rootstock_testnet (31), + Kovan (42), + Ethereum_Classic_mainnet(61), + Ethereum_Classic_testnet(62), + Geth_private_chains (1337); + + private int value; + + ChainEnum(int value) { + this.value = value; + } + + public int getValue() { + return value; + } + } + + public byte[] getRawHash() { + + byte[] plainMsg = this.getEncodedRaw(); + Keccak256 kec = new Keccak256(); + return kec.digest(plainMsg); + } + + public byte[] getHash() { + + byte[] plainMsg = this.getEncoded(); + Keccak256 kec = new Keccak256(); + return kec.digest(plainMsg); + } + + public int BruteRecoveryID2(ECDSASignature_ETH sig, byte[] messageHash, byte[] thisKey) + { + Log.e("ETH_KZ", BTCUtils.toHex(thisKey)); + int recId = -1; + for (int i = 0; i < 4; i++) { + byte[] recK = CryptoUtil.recoverPubBytesFromSignature(i, sig, messageHash); + + if(recK == null) + { + continue; + } + + Log.e("ETH_k "+String.valueOf(i), BTCUtils.toHex(recK)); + if (Arrays.equals(recK, thisKey)) { + recId = i; + recId +=27; + break; + } + } + return recId; + } + + public int BruteRecoveryID(ECKey.ECDSASignature sig, Sha256Hash messageHash, byte[] thisKey) + { + Log.e("ETH_KZ", BTCUtils.toHex(thisKey)); + int recId = -1; + for (int i = 0; i < 4; i++) { + ECKey k = ECKey.recoverFromSignature(i, sig, messageHash, false); + + if(k == null) + continue; + byte[] recK = k.getPubKey(); + Log.e("ETH_k "+String.valueOf(i), BTCUtils.toHex(recK)); + if (k != null && Arrays.equals(recK, thisKey)) { + recId = i; + break; + } + } + return recId; + } + + // signed TX + public byte[] getEncoded() { + + // parse null as 0 for nonce + byte[] nonce = null; + if (this.nonce == null || this.nonce.length == 1 && this.nonce[0] == 0) { + nonce = RLP.encodeElement(null); + } else { + nonce = RLP.encodeElement(this.nonce); + } + byte[] gasPrice = RLP.encodeElement(this.gasPrice); + byte[] gasLimit = RLP.encodeElement(this.gasLimit); + byte[] receiveAddress = RLP.encodeElement(this.receiveAddress); + byte[] value = RLP.encodeElement(this.value); + byte[] data = RLP.encodeElement(this.data); + + byte[] v, r, s; + + if (signature != null) { + int encodeV; + if (chainId == null) { + encodeV = signature.v; + } else { + encodeV = signature.v - LOWER_REAL_V; + encodeV += chainId * 2 + CHAIN_ID_INC; + } + v = RLP.encodeInt(encodeV); + r = RLP.encodeElement(BigIntegers.asUnsignedByteArray(signature.r)); + s = RLP.encodeElement(BigIntegers.asUnsignedByteArray(signature.s)); + } else { + // Since EIP-155 use chainId for v + v = chainId == null ? RLP.encodeElement(EMPTY_BYTE_ARRAY) : RLP.encodeInt(chainId); + r = RLP.encodeElement(EMPTY_BYTE_ARRAY); + s = RLP.encodeElement(EMPTY_BYTE_ARRAY); + } + + this.rlpEncoded = RLP.encodeList(nonce, gasPrice, gasLimit, + receiveAddress, value, data, v, r, s); + + //this.hash = this.getHash(); + + return rlpEncoded; + } + + // unsigned TX + public byte[] getEncodedRaw() { + // parse null as 0 for nonce + byte[] nonce = null; + if (this.nonce == null || this.nonce.length == 1 && this.nonce[0] == 0) { + nonce = RLP.encodeElement(null); + } else { + nonce = RLP.encodeElement(this.nonce); + } + byte[] gasPrice = RLP.encodeElement(this.gasPrice); + byte[] gasLimit = RLP.encodeElement(this.gasLimit); + byte[] receiveAddress = RLP.encodeElement(this.receiveAddress); + byte[] value = RLP.encodeElement(this.value); + byte[] data = RLP.encodeElement(this.data); + + // Since EIP-155 use chainId for v + if (chainId == null) { + rlpRaw = RLP.encodeList(nonce, gasPrice, gasLimit, receiveAddress, + value, data); + } else { + byte[] v, r, s; + v = RLP.encodeInt(chainId); + r = RLP.encodeElement(EMPTY_BYTE_ARRAY); + s = RLP.encodeElement(EMPTY_BYTE_ARRAY); + rlpRaw = RLP.encodeList(nonce, gasPrice, gasLimit, receiveAddress, + value, data, v, r, s); + } + return rlpRaw; + } +} diff --git a/app/src/main/java/com/tangem/wallet/Electrum_Request.java b/app/src/main/java/com/tangem/wallet/Electrum_Request.java new file mode 100644 index 0000000000..ae5a34cbde --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/Electrum_Request.java @@ -0,0 +1,184 @@ +package com.tangem.wallet; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +/** + * Created by dvol on 16.07.2017. + */ + +public class Electrum_Request { + public static final String METHOD_GetBalance = "blockchain.address.get_balance"; + public static final String METHOD_ListUnspent = "blockchain.address.listunspent"; + public static final String METHOD_GetHistory = "blockchain.address.get_history"; + public static final String METHOD_GetTransaction = "blockchain.transaction.get"; + public static final String METHOD_GetHeader = "blockchain.block.get_header"; + public static final String METHOD_SendTransaction = "blockchain.transaction.broadcast"; + public static final String METHOD_GetFee = "blockchain.estimatefee"; + + + + public JSONObject jsRequestData; + public String answerData; + public String error; + public String WalletAddress; + public String TxHash; + public String Host; + public int Port; + + private Electrum_Request() { + } + + public Electrum_Request(JSONObject jsRequest) { + try { + jsRequestData = new JSONObject(jsRequest.toString()); + } catch (JSONException e) { + e.printStackTrace(); + } + } + + public JSONObject getAnswer() { + try { + return new JSONObject(answerData); + } catch (Exception e) { + try { + return new JSONObject(String.format("[\"Error\":\"%s\"]", e.getMessage())); + } catch (JSONException e1) { + e1.printStackTrace(); + return null; + } + } + } + + public String getAsString() { + return jsRequestData.toString(); + } + + public void setID(int value) { + try { + jsRequestData.put("id", String.format("%d", value)); + } catch (JSONException e) { + e.printStackTrace(); + } + } + + public int getID() { + try { + return jsRequestData.getInt("id"); + } catch (JSONException e) { + e.printStackTrace(); + return 0; + } + } + + public static Electrum_Request CheckBalance(String wallet) { + Electrum_Request request = new Electrum_Request(); + try { + request.WalletAddress=wallet; + request.jsRequestData = new JSONObject("{ \"method\":\"" + METHOD_GetBalance + "\", \"params\":[\"" + wallet + "\"] }"); + } catch (JSONException e) { + e.printStackTrace(); + request.error = e.toString(); + } + return request; + } + + + public static Electrum_Request GetFee(String wallet) { + Electrum_Request request = new Electrum_Request(); + try{ + request.WalletAddress = wallet; //METHOD_GetFee + request.jsRequestData = new JSONObject("{ \"method\":\"" + METHOD_GetFee + "\", \"params\":[\"" + 6 + "\"] }"); + } + catch(JSONException e) + { + e.printStackTrace(); + request.error = e.toString(); + } + return request; + } + + public static Electrum_Request GetHeader(String wallet, String height) { + Electrum_Request request = new Electrum_Request(); + try { + request.WalletAddress=wallet; + request.jsRequestData = new JSONObject("{ \"method\":\"" + METHOD_GetHeader + "\", \"params\":[\"" + height + "\"] }"); + } catch (JSONException e) { + e.printStackTrace(); + request.error = e.toString(); + } + return request; + } + + public static Electrum_Request ListUnspent(String wallet) { + Electrum_Request request = new Electrum_Request(); + try { + request.WalletAddress=wallet; + request.jsRequestData = new JSONObject("{ \"method\":\"" + METHOD_ListUnspent + "\", \"params\":[\"" + wallet + "\"] }"); + } catch (JSONException e) { + e.printStackTrace(); + request.error = e.toString(); + } + return request; + } + + public static Electrum_Request Broadcast(String wallet, String tx) { + Electrum_Request request = new Electrum_Request(); + try { + request.WalletAddress=wallet; + request.jsRequestData = new JSONObject("{ \"method\":\"" + METHOD_SendTransaction + "\", \"params\":[\"" + tx + "\"] }"); + } catch (JSONException e) { + e.printStackTrace(); + request.error = e.toString(); + } + return request; + } + + public static Electrum_Request ListHistory(String wallet) { + Electrum_Request request = new Electrum_Request(); + try { + request.WalletAddress=wallet; + request.jsRequestData = new JSONObject("{ \"method\":\"" + METHOD_GetHistory + "\", \"params\":[\"" + wallet + "\"] }"); + } catch (JSONException e) { + e.printStackTrace(); + request.error = e.toString(); + } + return request; + } + + public static Electrum_Request GetTransaction(String wallet, String tx_hash) { + Electrum_Request request = new Electrum_Request(); + try { + request.WalletAddress=wallet; + request.TxHash = tx_hash; + request.jsRequestData = new JSONObject("{ \"method\":\"" + METHOD_GetTransaction + "\", \"params\":[\"" + tx_hash + "\"] }"); + } catch (JSONException e) { + e.printStackTrace(); + request.error = e.toString(); + } + return request; + } + + public boolean isMethod(String methodName) throws JSONException { + return jsRequestData.getString("method").equals(methodName); + } + + public JSONArray getParams() throws JSONException { + return jsRequestData.getJSONArray("params"); + } + + public JSONObject getResult() throws JSONException { + return getAnswer().getJSONObject("result"); + } + + public String getResultString() throws JSONException { + return getAnswer().getString("result"); + } + + public JSONArray getResultArray() throws JSONException { + return getAnswer().getJSONArray("result"); + } + + +} diff --git a/app/src/main/java/com/tangem/wallet/Electrum_Task.java b/app/src/main/java/com/tangem/wallet/Electrum_Task.java new file mode 100644 index 0000000000..ec9512a2a5 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/Electrum_Task.java @@ -0,0 +1,116 @@ +package com.tangem.wallet; + +import android.os.AsyncTask; +import android.util.Log; + +import java.io.BufferedReader; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.net.InetAddress; +import java.net.Socket; +import java.util.ArrayList; +import java.util.List; + +/** + * Created by dvol on 16.07.2017. + */ + +public class Electrum_Task extends AsyncTask> { + public static final String logTag = "Electrum"; + //public static final String Host = /*"hsmiths.changeip.net";*/ "testnetnode.arihanc.com"; + //public static final int Port = /*8080*/51001; + private int reqID = 1; + private String Host = ""; + private int Port = 0; + OutputStreamWriter out; + BufferedReader in; + + SharedData sharedCounter = null; + + + public Electrum_Task(String host, int port) { + super(); + Host = host; + Port = port; + } + + public Electrum_Task(String host, int port, SharedData sharedCounter) { + super(); + Host = host; + Port = port; + this.sharedCounter = sharedCounter; + } + + @Override + protected List doInBackground(Electrum_Request... requests) { + List result = new ArrayList<>(); + for (int i = 0; i < requests.length; i++) { + result.add(requests[i]); + } + try { + + InetAddress serverAddress = InetAddress.getByName(Host); + Log.v(logTag, "Connecting..."+Host); + Socket socket = new Socket(serverAddress, Port); + socket.setSoTimeout(5000); + try { + OutputStream os = socket.getOutputStream(); + out = new OutputStreamWriter(os, "UTF-8"); + Log.v(logTag, "Connected"); + InputStream is = socket.getInputStream(); + in = new BufferedReader(new InputStreamReader(is)); + + publishProgress(5); + + for (int i = 0; i < requests.length; i++) { + requests[i].setID(reqID++); + doRequest(requests[i]); + publishProgress(5 + 90 * (i + 1) / requests.length); + } + + publishProgress(100); + + } catch (Exception e) { + Log.e(logTag, "Error: ", e); + } finally { + socket.close(); + } + } catch (Exception e) { + Log.e(logTag, "Error: ", e); + for (int i = 0; i < requests.length; i++) { + result.get(i).error = e.toString(); + } + } + return result; + } + + private void doRequest(Electrum_Request request) { + try { + + Log.v(logTag, "<< " + request.getAsString()); + + out.write(request.getAsString() + "\n"); + out.flush(); + + request.answerData = in.readLine(); + request.Host=Host; + request.Port=Port; + if (request.answerData != null) { + Log.v(logTag, ">> " + request.answerData); + } else { + request.error = "No answer from server"; + Log.v(logTag, ">> "); + } + } catch (Exception e) { + request.error = e.toString(); + } + } + + public String getValidationNodeDescription() { + return "Electrum, "+Host+":"+String.valueOf(Port); + } + + +} diff --git a/app/src/main/java/com/tangem/wallet/EmptyWalletActivity.java b/app/src/main/java/com/tangem/wallet/EmptyWalletActivity.java new file mode 100644 index 0000000000..4413e2844b --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/EmptyWalletActivity.java @@ -0,0 +1,356 @@ +package com.tangem.wallet; + +import android.app.Activity; +import android.content.Intent; +import android.content.res.ColorStateList; +import android.graphics.Color; +import android.nfc.NfcAdapter; +import android.nfc.Tag; +import android.nfc.tech.IsoDep; +import android.os.Bundle; +import android.support.v7.app.AppCompatActivity; +import android.util.Log; +import android.view.View; +import android.widget.Button; +import android.widget.ImageView; +import android.widget.ProgressBar; +import android.widget.TextView; +import android.widget.Toast; + +import com.tangem.cardReader.CardProtocol; +import com.tangem.cardReader.NfcManager; +import com.tangem.cardReader.Util; + +public class EmptyWalletActivity extends AppCompatActivity implements NfcAdapter.ReaderCallback, CardProtocol.Notifications { + + private static final int REQUEST_CODE_CREATE_NEW_WALLET_ACTIVITY = 2; + private static final int REQUEST_CODE_REQUEST_PIN2 = 3; + private static final int REQUEST_CODE_VERIFY_CARD = 4; + Tangem_Card mCard; + TextView tvCardID, tvIssuer, tvIssuerData, tvBlockchain; + ProgressBar progressBar; + ImageView ivBlockchain, ivPIN, ivPIN2orSecurityDelay, ivDeveloperVersion; + + private NfcManager mNfcManager; + private final String logTag = "EmptyWalletActivity"; + private boolean lastReadSuccess = true; + private VerifyCardTask verifyCardTask = null; + private int requestPIN2Count = 0; + + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_empty_wallet); + + MainActivity.commonInit(getApplicationContext()); + mNfcManager = new NfcManager(this, this); + + mCard = new Tangem_Card(getIntent().getStringExtra("UID")); + mCard.LoadFromBundle(getIntent().getExtras().getBundle("Card")); + + tvCardID = findViewById(R.id.tvCardID); + tvCardID.setText(mCard.getCIDDescription()); + + tvIssuer = findViewById(R.id.tvIssuer); + tvIssuerData = findViewById(R.id.tvIssuerData); + tvBlockchain = findViewById(R.id.tvBlockchain); + + tvIssuer.setText(mCard.getIssuerDescription()); + tvIssuerData.setText(mCard.getIssuerDataDescription()); + + //tvBlockchain.setText(mCard.getBlockchain().getOfficialName()); + tvBlockchain.setText(mCard.getBlockchainName()); + progressBar = findViewById(R.id.progressBar); + + ivBlockchain = findViewById(R.id.imgBlockchain); + ivPIN = findViewById(R.id.imgPIN); + ivPIN2orSecurityDelay = findViewById(R.id.imgPIN2orSecurityDelay); + ivDeveloperVersion = findViewById(R.id.imgDeveloperVersion); + + ivBlockchain.setImageResource(mCard.getBlockchain().getImageResource(this, mCard.getTokenSymbol())); + + if (mCard.useDefaultPIN1()) { + ivPIN.setImageResource(R.drawable.unlock_pin1); + ivPIN.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Toast.makeText(EmptyWalletActivity.this, "This banknote is protected by default PIN1 code", Toast.LENGTH_LONG).show(); + } + }); + } else { + ivPIN.setImageResource(R.drawable.lock_pin1); + ivPIN.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Toast.makeText(EmptyWalletActivity.this, "This banknote is protected by user's PIN1 code", Toast.LENGTH_LONG).show(); + } + }); + } + + if (mCard.getPauseBeforePIN2() > 0 && (mCard.useDefaultPIN2() || !mCard.useSmartSecurityDelay())) { + ivPIN2orSecurityDelay.setImageResource(R.drawable.timer); + ivPIN2orSecurityDelay.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Toast.makeText(EmptyWalletActivity.this, String.format("This banknote will enforce %.0f seconds security delay for all operations requiring PIN2 code", mCard.getPauseBeforePIN2() / 1000.0), Toast.LENGTH_LONG).show(); + } + }); + + } else if (mCard.useDefaultPIN2()) { + ivPIN2orSecurityDelay.setImageResource(R.drawable.unlock_pin2); + ivPIN2orSecurityDelay.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Toast.makeText(EmptyWalletActivity.this, "This banknote is protected by default PIN2 code", Toast.LENGTH_LONG).show(); + } + }); + } else { + ivPIN2orSecurityDelay.setImageResource(R.drawable.lock_pin2); + ivPIN2orSecurityDelay.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Toast.makeText(EmptyWalletActivity.this, "This banknote is protected by user's PIN2 code", Toast.LENGTH_LONG).show(); + } + }); + } + + + if (mCard.useDevelopersFirmware()) { + ivDeveloperVersion.setImageResource(R.drawable.ic_developer_version); + ivDeveloperVersion.setVisibility(View.VISIBLE); + ivDeveloperVersion.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Toast.makeText(EmptyWalletActivity.this, "Unlocked banknote, only for development use", Toast.LENGTH_LONG).show(); + } + }); + } else { + ivDeveloperVersion.setVisibility(View.INVISIBLE); + } + + Button btnNewWallet = findViewById(R.id.btnNewWallet); + btnNewWallet.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + //CreateSelectBlockchainDialog(); + requestPIN2Count = 0; + Intent intent = new Intent(getBaseContext(), RequestPINActivity.class); + intent.putExtra("mode", RequestPINActivity.Mode.RequestPIN2.toString()); + intent.putExtra("UID", mCard.getUID()); + intent.putExtra("Card", mCard.getAsBundle()); + startActivityForResult(intent, REQUEST_CODE_REQUEST_PIN2); + } + }); + + if (getIntent().getExtras().containsKey(NfcAdapter.EXTRA_TAG)) { + Tag tag = getIntent().getParcelableExtra(NfcAdapter.EXTRA_TAG); + if (tag != null) { + onTagDiscovered(tag); + } + } + } + + + private void doCreateNewWallet() { + Intent intent = new Intent(this, CreateNewWalletActivity.class); + + intent.putExtra("UID", mCard.getUID()); + intent.putExtra("Card", mCard.getAsBundle()); + +// intent.putExtra("newPIN",mCard.getPIN()); +// intent.putExtra("newPIN2","12345678"); + startActivityForResult(intent, REQUEST_CODE_CREATE_NEW_WALLET_ACTIVITY); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + if (requestCode == REQUEST_CODE_CREATE_NEW_WALLET_ACTIVITY) { + if (resultCode == Activity.RESULT_OK) { + + if (data != null) { + data.putExtra("modification", "updateAndViewCard"); + data.putExtra("updateDelay", 0); + setResult(Activity.RESULT_OK, data); + } + finish(); + } else { + if (data != null && data.getExtras().containsKey("UID") && data.getExtras().containsKey("Card")) { + Tangem_Card updatedCard = new Tangem_Card(data.getStringExtra("UID")); + updatedCard.LoadFromBundle(data.getBundleExtra("Card")); + mCard = updatedCard; + } + if (resultCode == CreateNewWalletActivity.RESULT_INVALID_PIN && requestPIN2Count < 2) { + requestPIN2Count++; + Intent intent = new Intent(getBaseContext(), RequestPINActivity.class); + intent.putExtra("mode", RequestPINActivity.Mode.RequestPIN2.toString()); + intent.putExtra("UID", mCard.getUID()); + intent.putExtra("Card", mCard.getAsBundle()); + startActivityForResult(intent, REQUEST_CODE_REQUEST_PIN2); + return; + } + } + setResult(resultCode, data); + finish(); + } else if (requestCode == REQUEST_CODE_REQUEST_PIN2) { + if (resultCode == Activity.RESULT_OK) { + doCreateNewWallet(); + } + } + + } + + @Override + public void onTagDiscovered(Tag tag) { + try { + final IsoDep isoDep = IsoDep.get(tag); + if (isoDep == null) { + throw new CardProtocol.TangemException(getString(R.string.wrong_tag_err)); + } + byte UID[] = tag.getId(); + String sUID = Util.byteArrayToHexString(UID); + if (!mCard.getUID().equals(sUID)) { + Log.d(logTag, "Invalid UID: " + sUID); + mNfcManager.IgnoreTag(isoDep.getTag()); + return; + } else { + Log.v(logTag, "UID: " + sUID); + } + + if (lastReadSuccess) { + isoDep.setTimeout(1000); + } else { + isoDep.setTimeout(65000); + } + //lastTag = tag; + verifyCardTask = new VerifyCardTask(this, mCard, mNfcManager, isoDep, this); + verifyCardTask.start(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public void OnReadStart(CardProtocol cardProtocol) { + progressBar.post(new Runnable() { + @Override + public void run() { + progressBar.setVisibility(View.VISIBLE); + progressBar.setProgress(5); + } + }); + } + + public void OnReadFinish(final CardProtocol cardProtocol) { + + verifyCardTask = null; + + if (cardProtocol != null) { + if (cardProtocol.getError() == null) { + progressBar.post(new Runnable() { + @Override + public void run() { + progressBar.setProgress(100); + progressBar.setProgressTintList(ColorStateList.valueOf(Color.GREEN)); + Intent intent = new Intent(EmptyWalletActivity.this, VerifyCardActivity.class); + // TODO обновить карту mCard + intent.putExtra("UID", cardProtocol.getCard().getUID()); + intent.putExtra("Card", cardProtocol.getCard().getAsBundle()); + startActivityForResult(intent, REQUEST_CODE_VERIFY_CARD); + //addCard(cardProtocol.getCard()); + } + }); + } else { + // remove last UIDs because of error and no card read + progressBar.post(new Runnable() { + @Override + public void run() { + lastReadSuccess = false; + if (cardProtocol.getError() instanceof CardProtocol.TangemException_ExtendedLengthNotSupported) { + if (!NoExtendedLengthSupportDialog.allreadyShowed) { + new NoExtendedLengthSupportDialog().show(getFragmentManager(), "NoExtendedLengthSupportDialog"); + } + } else { + Toast.makeText(EmptyWalletActivity.this, "Try to scan again", Toast.LENGTH_LONG).show(); + } + progressBar.setProgress(100); + progressBar.setProgressTintList(ColorStateList.valueOf(Color.RED)); + } + }); + } + } + + progressBar.postDelayed(new Runnable() { + @Override + public void run() { + try { + progressBar.setProgress(0); + progressBar.setProgressTintList(ColorStateList.valueOf(Color.DKGRAY)); + progressBar.setVisibility(View.INVISIBLE); + } catch (Exception e) { + e.printStackTrace(); + } + } + }, 500); + } + + public void OnReadProgress(CardProtocol protocol, final int progress) { + progressBar.post(new Runnable() { + @Override + public void run() { + progressBar.setProgress(progress); + } + }); + } + + public void OnReadCancel() { + + verifyCardTask = null; + + progressBar.postDelayed(new Runnable() { + @Override + public void run() { + try { + progressBar.setProgress(0); + progressBar.setProgressTintList(ColorStateList.valueOf(Color.DKGRAY)); + progressBar.setVisibility(View.INVISIBLE); + } catch (Exception e) { + e.printStackTrace(); + } + } + }, 500); + } + + @Override + public void OnReadWait(int msec) { + WaitSecurityDelayDialog.OnReadWait(this, msec); + } + + @Override + public void OnReadBeforeRequest(int timeout) { + WaitSecurityDelayDialog.onReadBeforeRequest(this, timeout); + } + + @Override + public void OnReadAfterRequest() { + WaitSecurityDelayDialog.onReadAfterRequest(this); + } + + + @Override + public void onResume() { + super.onResume(); + mNfcManager.onResume(); + } + + @Override + public void onPause() { + super.onPause(); + mNfcManager.onPause(); + } + + @Override + public void onStop() { + super.onStop(); + mNfcManager.onStop(); + } +} diff --git a/app/src/main/java/com/tangem/wallet/EthEngine.java b/app/src/main/java/com/tangem/wallet/EthEngine.java new file mode 100644 index 0000000000..56be142acc --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/EthEngine.java @@ -0,0 +1,392 @@ +package com.tangem.wallet; + +import android.net.Uri; +import android.util.Log; + +import com.tangem.cardReader.CardProtocol; +import com.tangem.cardReader.TLV; + +import org.bitcoinj.core.ECKey; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.math.RoundingMode; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.text.DecimalFormat; +import java.util.Arrays; +import java.util.Date; + +import static com.tangem.wallet.FormatUtil.GetDecimalFormat; + +/** + * Created by Ilia on 15.02.2018. + */ + +public class EthEngine extends CoinEngine{ + + public String GetNextNode(Tangem_Card mCard) + { + return "abc1.hsmiths.com"; + } + + public int GetNextNodePort(Tangem_Card mCard) + { + return 60001; + } + + public String GetNode(Tangem_Card mCard) + { + return "abc1.hsmiths.com"; + } + + public int GetNodePort(Tangem_Card mCard) + { + return 60001; + } + + public void SwitchNode(Tangem_Card mCard) + { + } + + public boolean InOutPutVisible() + { + return false; + } + + public String GetBalanceCurrency(Tangem_Card card) + { + return "ETH"; + } + + public boolean AwaitingConfirmation(Tangem_Card card) + { + return false; + } + + public String GetFeeCurrency() + { + return "Gwei"; + } + + public boolean IsNeedCheckNode() + { + return false; + } + + BigDecimal convertToEth(String value) + { + BigInteger m = new BigInteger(value, 10); + BigDecimal n = new BigDecimal(m); + BigDecimal d = n.divide(new BigDecimal("1000000000000000000")); + d = d.setScale(8, RoundingMode.DOWN); + return d; + } + + public int GetTokenDecimals(Tangem_Card card) + { + return 0; + } + + public String GetContractAddress(Tangem_Card card) + { + return ""; + } + + public boolean ValdateAddress(String address, Tangem_Card card) { + + if (address == null || address.isEmpty()) + { + return false; + } + + if(!address.startsWith("0x")&&!address.startsWith("0X")) + { + return false; + } + + if(address.length()!=42) + { + return false; + } + + return true; + } + public String GetBalanceValue(Tangem_Card mCard) + { + String dec = mCard.getDecimalBalance(); + BigDecimal d = convertToEth(dec); + String s = d.toString(); + + String pattern = "#0.000"; // If you like 4 zeros + DecimalFormat myFormatter = new DecimalFormat(pattern); + String output = myFormatter.format(d); + return output; + } + + public static String getAmountEquivalentDescriptionETH(BigDecimal amount, float rateValue) { + if (amount == null || amount.compareTo(BigDecimal.ZERO) == 0) + return ""; + + if (rateValue > 0) { + BigDecimal biRate = new BigDecimal(rateValue); + BigDecimal exchangeCurs = biRate.multiply(amount); + exchangeCurs = exchangeCurs.setScale(2, RoundingMode.DOWN); + return "≈ USD  " + exchangeCurs.toString(); + } else { + return "≈ USD  ---"; + } + } + + public static String getAmountEquivalentDescriptionETH(Double amount, float rate) { + if (amount == 0) + return ""; + amount = amount / 100000; + if (rate > 0) { + return String.format("≈ USD %.2f", amount * rate); + } else { + return "≈ USD  ---"; + } + } + + + + @Override + public String GetBalanceEquivalent(Tangem_Card mCard) { + String dec = mCard.getDecimalBalance(); + BigDecimal d = convertToEth(dec); + return getAmountEquivalentDescriptionETH(d, mCard.getRate()); + } + + @Override + public String GetBalance(Tangem_Card mCard) { + if(!HasBalanceInfo(mCard)){ + return "-- -- -- " + GetBalanceCurrency(mCard); + } + + String output = GetBalanceValue(mCard); + String s = output + " " + GetBalanceCurrency(mCard); + return s; + } + + public Long GetBalanceLong(Tangem_Card mCard) + { + return mCard.getBalance(); + } + + public String GetBalanceWithAlter(Tangem_Card mCard) + { + return GetBalance(mCard); + } + + public boolean IsBalanceAlterNotZero(Tangem_Card card) + { + return true; + } + + public boolean IsBalanceNotZero(Tangem_Card card) + { + String balance = card.getDecimalBalance(); + if(balance == null || balance == "") + return false; + + BigDecimal bi = new BigDecimal(balance); + + if (BigDecimal.ZERO.compareTo(bi) == 0) + return false; + + return true; + } + + @Override + public String ConvertByteArrayToAmount(Tangem_Card mCard, byte[] bytes) throws Exception { + throw new Exception("Not implemented"); + } + + @Override + public byte[] ConvertAmountToByteArray(Tangem_Card mCard, String amount) throws Exception { + throw new Exception("Not implemented"); + } + + @Override + public String GetAmountDescription(Tangem_Card mCard, String amount) throws Exception { + throw new Exception("Not implemented"); + } + + public String GetAmountEqualentDescriptor(Tangem_Card mCard, String value) + { + BigDecimal d = new BigDecimal(value); + return getAmountEquivalentDescriptionETH(d, mCard.getRate()); + } + + public boolean CheckAmount(Tangem_Card card, String amount) throws Exception + { + DecimalFormat decimalFormat = GetDecimalFormat(); + BigDecimal amountValue = (BigDecimal) decimalFormat.parse(amount); //new BigDecimal(strAmount); + BigDecimal maxValue = new BigDecimal(GetBalanceValue(card)); + if(amountValue.compareTo(maxValue) > 0 ) + { + return false; + } + + return true; + } + + public boolean HasBalanceInfo(Tangem_Card card) + { + return card.hasBalanceInfo(); + } + + public Uri getShareWalletURI(Tangem_Card mCard) + { + return Uri.parse("" + mCard.getWallet()); + } + + public Uri getShareWalletURIExplorer(Tangem_Card mCard) + { + if(mCard.getBlockchain() == Blockchain.EthereumTestNet) + return Uri.parse("https://rinkeby.etherscan.io/address/" + mCard.getWallet()); + else + return Uri.parse("https://etherscan.io/address/" + mCard.getWallet()); + } + + public boolean CheckUnspentTransaction(Tangem_Card mCard) + { + return true; + } + + + public boolean CheckAmountValie(Tangem_Card mCard, String amountValue, String feeValue, Long minFeeInInternalUnits) + { + Long fee = null; + Long amount = null; + try { + amount = mCard.InternalUnitsFromString(amountValue); + fee = mCard.InternalUnitsFromString(feeValue); + } catch (Exception e) { + e.printStackTrace(); + return false; + } + + if(fee == null || amount == null) + return false; + + if(fee == 0 || amount ==0) + return false; + + + if(fee < minFeeInInternalUnits) + return false; + + + BigDecimal tmpFee = new BigDecimal(feeValue); + BigDecimal tmpAmount = new BigDecimal(amountValue); + tmpAmount = tmpAmount.multiply(new BigDecimal("1000000000")); + + if (tmpFee.compareTo(tmpAmount) > 0) + return false; + + return true; + } + + public String EvaluteFeeEquivalent(Tangem_Card mCard, String fee) + { + BigDecimal gweFee = new BigDecimal(fee); + gweFee = gweFee.divide(new BigDecimal("1000000000")); + gweFee = gweFee.setScale(18, RoundingMode.DOWN); + return GetAmountEqualentDescriptor(mCard, gweFee.toString()); + } + + public String calculateAddress(Tangem_Card mCard, byte[] pkUncompressed) throws NoSuchProviderException, NoSuchAlgorithmException { + Keccak256 kec = new Keccak256(); + int lenPk = pkUncompressed.length; + if (lenPk < 2) { + throw new IllegalArgumentException("Uncompress public key length is invald"); + } + byte[] cleanKey = new byte[lenPk - 1]; + for (int i = 0; i < cleanKey.length; ++i) { + cleanKey[i] = pkUncompressed[i + 1]; + } + byte[] r = kec.digest(cleanKey); + + byte[] address = new byte[20]; + for (int i = 0; i < 20; ++i) { + address[i] = r[i + 12]; + } + + return String.format("0x%s", BTCUtils.toHex(address)); + } + + public byte[] Sign(String feeValue, String amountValue, String toValue, Tangem_Card mCard, CardProtocol protocol) throws Exception { + + BigInteger nonceValue = mCard.GetConfirmTXCount(); + byte[] pbKey = mCard.getWalletPublicKey(); + boolean flag = (mCard.getSigningMethod()== Tangem_Card.SigningMethod.Sign_Hash_Validated_By_Issuer); + Issuer issuer = mCard.getIssuer(); + + + BigInteger fee = new BigInteger(feeValue, 10); + + BigDecimal amountDec = new BigDecimal(amountValue); + amountDec = amountDec.multiply(new BigDecimal("1000000000")); + + + BigInteger amount = amountDec.toBigInteger(); //new BigInteger(amountValue, 10); + amount = amount.subtract(fee); + + BigInteger nonce = nonceValue; + BigInteger gasPrice = fee.divide(BigInteger.valueOf(21000)); + BigInteger gasLimit = BigInteger.valueOf(21000); + Integer chainId = mCard.getBlockchain() == Blockchain.Ethereum ? ETH_Transaction.ChainEnum.Mainnet.getValue() : ETH_Transaction.ChainEnum.Rinkeby.getValue(); + + Long multiplicator = 1000000000L; + amount = amount.multiply(BigInteger.valueOf(multiplicator)); + gasPrice = gasPrice.multiply(BigInteger.valueOf(multiplicator)); + + String to = toValue; + + if (to.startsWith("0x") || to.startsWith("0X")) { + to = to.substring(2); + } + + ETH_Transaction tx = ETH_Transaction.create(to, amount, nonce, gasPrice, gasLimit, chainId); + + byte[][] hashesForSign = new byte[1][]; + byte[] for_hash = tx.getRawHash(); + hashesForSign[0] = for_hash; + + byte[] signFromCard = null; + try { + signFromCard = protocol.run_SignHashes(PINStorage.getPIN2(), hashesForSign, flag, null, issuer).getTLV(TLV.Tag.TAG_Signature).Value; + // TODO slice signFromCard to hashes.length parts + } catch (Exception ex) { + Log.e("ETH", ex.getMessage()); + return null; + } + + LastSignStorage.setLastSignDate(mCard.getWallet(), new Date()); + + BigInteger r = new BigInteger(1, Arrays.copyOfRange(signFromCard, 0, 32)); + BigInteger s = new BigInteger(1, Arrays.copyOfRange(signFromCard, 32, 64)); + s = CryptoUtil.toCanonicalised(s); + + boolean f = ECKey.verify(for_hash, new ECKey.ECDSASignature(r, s), pbKey); + + if(!f) + { + Log.e("ETH-CHECK", "Sign Failed."); + } + + tx.signature = new ECDSASignature_ETH(r, s); + int v = tx.BruteRecoveryID2(tx.signature, for_hash, pbKey); + if (v != 27 && v != 28) { + Log.e("ETH", "invalid v"); + return null; + } + tx.signature.v = (byte) v; + Log.e("ETH_v", String.valueOf(v)); + + byte[] realTX = tx.getEncoded(); + return realTX; + } +} diff --git a/app/src/main/java/com/tangem/wallet/ExchangeRequest.java b/app/src/main/java/com/tangem/wallet/ExchangeRequest.java new file mode 100644 index 0000000000..942cdbe112 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/ExchangeRequest.java @@ -0,0 +1,92 @@ +package com.tangem.wallet; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +/** + * Created by dvol on 16.07.2017. + */ + +public class ExchangeRequest { + public JSONObject jsRequestData; + public String answerData; + public String error; + public String WalletAddress; + public String currency; + public String currencyAlter; + + private ExchangeRequest() { + } + + public ExchangeRequest(JSONObject jsRequest) { + try { + jsRequestData = new JSONObject(jsRequest.toString()); + } catch (JSONException e) { + e.printStackTrace(); + } + } + + public JSONObject getAnswer() { + try { + return new JSONObject(answerData); + } catch (Exception e) { + try { + return new JSONObject(String.format("[\"Error\":\"%s\"]", e.getMessage())); + } catch (JSONException e1) { + e1.printStackTrace(); + return null; + } + } + } + + public JSONArray getAnswerList() throws JSONException { + return new JSONArray(answerData); + } + + public String getAsString() { + return jsRequestData.toString(); + } + + public void setID(int value) { + try { + jsRequestData.put("id", String.format("%d", value)); + } catch (JSONException e) { + e.printStackTrace(); + } + } + + public int getID() { + try { + return jsRequestData.getInt("id"); + } catch (JSONException e) { + e.printStackTrace(); + return 0; + } + } + + public static ExchangeRequest GetRate(String wallet, String currency, String alterCurrency) { + ExchangeRequest request = new ExchangeRequest(); + request.WalletAddress=wallet; + request.currency = currency; + request.currencyAlter = alterCurrency; + return request; + } + + + public JSONArray getParams() throws JSONException { + return jsRequestData.getJSONArray("params"); + } + + public JSONObject getResult() throws JSONException { + return getAnswer().getJSONObject("result"); + } + + public String getResultString() throws JSONException { + return getAnswer().getString("result"); + } + + public JSONArray getResultArray() throws JSONException { + return getAnswer().getJSONArray("result"); + } +} diff --git a/app/src/main/java/com/tangem/wallet/ExchangeTask.java b/app/src/main/java/com/tangem/wallet/ExchangeTask.java new file mode 100644 index 0000000000..a47d149d96 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/ExchangeTask.java @@ -0,0 +1,66 @@ +package com.tangem.wallet; + +/** + * Created by Ilia on 16.01.2018. + */ + +import android.os.AsyncTask; + +import com.tangem.wallet.ExchangeRequest; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.ArrayList; +import java.util.List; + +/** + * Created by Ilia on 04.12.2017. + */ + +public class ExchangeTask extends AsyncTask> { + public ExchangeTask() + { + + } + protected List doInBackground(ExchangeRequest... requests) { + List result = new ArrayList<>(); + for (int i = 0; i < requests.length; i++) { + result.add(requests[i]); + } + + for (ExchangeRequest request: result) + { + HttpURLConnection httpcon = null; + + try { + + URL url = new URL("https://api.coinmarketcap.com/v1/ticker/?convert=USD&lmit=10"); + httpcon = (HttpURLConnection) url.openConnection(); + httpcon.setRequestMethod("GET"); + + httpcon.connect(); + + BufferedReader in = new BufferedReader( + new InputStreamReader(httpcon.getInputStream())); + String inputLine; + StringBuffer response = new StringBuffer(); + + while ((inputLine = in.readLine()) != null) { + response.append(inputLine); + } + in.close(); + + request.answerData = response.toString(); + + } catch (Exception e) { + request.error = e.getMessage(); + } finally { + httpcon.disconnect(); + } + } + + return result; + } +} diff --git a/app/src/main/java/com/tangem/wallet/Fee_Request.java b/app/src/main/java/com/tangem/wallet/Fee_Request.java new file mode 100644 index 0000000000..103ed9fb39 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/Fee_Request.java @@ -0,0 +1,102 @@ +package com.tangem.wallet; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +/** + * Created by dvol on 16.07.2017. + */ + +public class Fee_Request { + public JSONObject jsRequestData; + public String answerData; + public String error; + public String WalletAddress; + public long txSize = 0; + + private Fee_Request() { + } + + public Fee_Request(JSONObject jsRequest) { + try { + jsRequestData = new JSONObject(jsRequest.toString()); + } catch (JSONException e) { + e.printStackTrace(); + } + } + + public JSONObject getAnswer() { + try { + return new JSONObject(answerData); + } catch (Exception e) { + try { + return new JSONObject(String.format("[\"Error\":\"%s\"]", e.getMessage())); + } catch (JSONException e1) { + e1.printStackTrace(); + return null; + } + } + } + + public String getAsString() { + return answerData; + } + + public static final int PRIORITY = 2; + public static final int NORMAL = 3; + public static final int MINIMAL = 6; + private int blockCount = NORMAL; + public void setBlockCount(int count + ) + { + blockCount = count; + } + + public int getBlockCount() + { + return blockCount; + } + + public void setID(int value) { + try { + jsRequestData.put("id", String.format("%d", value)); + } catch (JSONException e) { + e.printStackTrace(); + } + } + + public int getID() { + try { + return jsRequestData.getInt("id"); + } catch (JSONException e) { + e.printStackTrace(); + return 0; + } + } + + public static Fee_Request GetFee(String wallet, long txSize, int blockCount) { + Fee_Request request = new Fee_Request(); + request.WalletAddress=wallet; + request.txSize = txSize; + request.setBlockCount(blockCount); + return request; + } + + + public JSONArray getParams() throws JSONException { + return jsRequestData.getJSONArray("params"); + } + + public JSONObject getResult() throws JSONException { + return getAnswer().getJSONObject("result"); + } + + public String getResultString() throws JSONException { + return getAnswer().getString("result"); + } + + public JSONArray getResultArray() throws JSONException { + return getAnswer().getJSONArray("result"); + } +} diff --git a/app/src/main/java/com/tangem/wallet/Fee_Task.java b/app/src/main/java/com/tangem/wallet/Fee_Task.java new file mode 100644 index 0000000000..a8c5c35a94 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/Fee_Task.java @@ -0,0 +1,63 @@ +package com.tangem.wallet; + +import android.os.AsyncTask; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.ArrayList; +import java.util.List; + +/** + * Created by Ilia on 04.12.2017. + */ + +public class Fee_Task extends AsyncTask> { + + SharedData sharedCounter = null; + + public Fee_Task(SharedData sharedData) + { + sharedCounter = sharedData; + } + protected List doInBackground(Fee_Request... requests) { + List result = new ArrayList<>(); + for (int i = 0; i < requests.length; i++) { + result.add(requests[i]); + } + + for (Fee_Request request: result) + { + HttpURLConnection httpcon = null; + + try { + + URL url = new URL("https://estimatefee.com/n/"+String.valueOf(request.getBlockCount())); + httpcon = (HttpURLConnection) url.openConnection(); + httpcon.setRequestMethod("GET"); + + httpcon.connect(); + + BufferedReader in = new BufferedReader( + new InputStreamReader(httpcon.getInputStream())); + String inputLine; + StringBuffer response = new StringBuffer(); + + while ((inputLine = in.readLine()) != null) { + response.append(inputLine); + } + in.close(); + + request.answerData = response.toString(); + + } catch (Exception e) { + request.error = e.getMessage(); + } finally { + httpcon.disconnect(); + } + } + + return result; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/wallet/FingerprintHelper.java b/app/src/main/java/com/tangem/wallet/FingerprintHelper.java new file mode 100644 index 0000000000..b4fb689afe --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/FingerprintHelper.java @@ -0,0 +1,62 @@ +package com.tangem.wallet; + +import android.annotation.TargetApi; +import android.hardware.fingerprint.FingerprintManager; +import android.os.Build; +import android.os.CancellationSignal; + +/** + * Created by dtaka on 8/20/2016. + */ +@TargetApi(Build.VERSION_CODES.M) +public class FingerprintHelper extends FingerprintManager.AuthenticationCallback { + private FingerprintHelperListener listener; + + public FingerprintHelper(FingerprintHelperListener listener) { + this.listener = listener; + } + + private CancellationSignal cancellationSignal; + + public void startAuth(FingerprintManager manager, FingerprintManager.CryptoObject cryptoObject) { + cancellationSignal = new CancellationSignal(); + + try { + manager.authenticate(cryptoObject, cancellationSignal, 0, this, null); + } catch (SecurityException ex) { + listener.authenticationFailed("An error occurred:\n" + ex.getMessage()); + } catch (Exception ex) { + listener.authenticationFailed("An error occurred\n" + ex.getMessage()); + } + } + + public void cancel() { + if (cancellationSignal != null) + cancellationSignal.cancel(); + } + + interface FingerprintHelperListener { + public void authenticationFailed(String error); + public void authenticationSucceeded(FingerprintManager.AuthenticationResult result); + } + + @Override + public void onAuthenticationError(int errMsgId, CharSequence errString) { + listener.authenticationFailed("Authentication error\n" + errString); + } + + @Override + public void onAuthenticationHelp(int helpMsgId, CharSequence helpString) { + listener.authenticationFailed("Authentication help\n" + helpString); + } + + @Override + public void onAuthenticationFailed() { + listener.authenticationFailed("Authentication failed."); + } + + @Override + public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) { + listener.authenticationSucceeded(result); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/wallet/FormatUtil.java b/app/src/main/java/com/tangem/wallet/FormatUtil.java new file mode 100644 index 0000000000..4a21adb789 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/FormatUtil.java @@ -0,0 +1,51 @@ +package com.tangem.wallet; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; +import java.util.Locale; +import java.util.regex.Pattern; + +/** + * Created by Ilia on 15.02.2018. + */ + +public class FormatUtil { + public static long parseValue(String valueStr) throws NumberFormatException { + return new BigDecimal(valueStr).multiply(BigDecimal.valueOf(1_0000_0000)).setScale(0, BigDecimal.ROUND_HALF_DOWN).longValueExact(); + } + + public static String DoubleToString(double amount) + { + DecimalFormat myFormatter = GetDecimalFormat(); + String output = myFormatter.format(amount); + return output; + } + + public static DecimalFormat GetDecimalFormat() + { + DecimalFormatSymbols symbols = new DecimalFormatSymbols(); + symbols.setDecimalSeparator('.'); + + String pattern = "#0.######"; + DecimalFormat myFormatter = new DecimalFormat(pattern, symbols); + + myFormatter.setParseBigDecimal(true); + + return myFormatter; + } + + + public static long ConvertStringToLong(String caption) throws Exception { + + + BigDecimal d = new BigDecimal(caption); + d = d.multiply(new BigDecimal(100000)); + d = d.setScale(5); + BigInteger b = d.toBigInteger(); + long l = b.longValue(); + return l; + + } +} diff --git a/app/src/main/java/com/tangem/wallet/Infura_Request.java b/app/src/main/java/com/tangem/wallet/Infura_Request.java new file mode 100644 index 0000000000..1f3465b6e9 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/Infura_Request.java @@ -0,0 +1,197 @@ +package com.tangem.wallet; + +/** + * Created by Ilia on 19.12.2017. + */ + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + + +public class Infura_Request { + public static final String METHOD_ETH_GetBalance = "eth_getBalance"; + public static final String METHOD_ETH_GetOutTransactionCount = "eth_getTransactionCount"; + public static final String METHOD_ETH_GetGasPrice = "eth_gasPrice"; + public static final String METHOD_ETH_SendRawTransaction = "eth_sendRawTransaction"; + public static final String METHOD_ETH_Call = "eth_call"; + + public JSONObject jsRequestData; + public String answerData; + public String error; + public String WalletAddress; + public int Dec; + public String amount; + public Blockchain blockchain; + + public void setBlockchain(Blockchain value) { + blockchain = value; + } + + public Blockchain getBlockchain() + { + return blockchain; + } + + private Infura_Request() { + } + + public Infura_Request(JSONObject jsRequest) { + try { + jsRequestData = new JSONObject(jsRequest.toString()); + } catch (JSONException e) { + e.printStackTrace(); + } + } + + public JSONObject getAnswer() { + try { + return new JSONObject(answerData); + } catch (Exception e) { + try { + return new JSONObject(String.format("[\"Error\":\"%s\"]", e.getMessage())); + } catch (JSONException e1) { + e1.printStackTrace(); + return null; + } + } + } + + public String getAsString() { + return jsRequestData.toString(); + } + + public void setID(int value) { + try { + jsRequestData.put("id", value/*String.format("%d", value)*/); + } catch (JSONException e) { + e.printStackTrace(); + } + } + + public int getID() { + try { + return jsRequestData.getInt("id"); + } catch (JSONException e) { + e.printStackTrace(); + return 0; + } + } + + public boolean isMethod(String methodName) throws JSONException { + return jsRequestData.getString("method").equals(methodName); + } + + public static Infura_Request GetBalance(String wallet) { + Infura_Request request = new Infura_Request(); + try { + request.WalletAddress=wallet; + request.jsRequestData = new JSONObject("{ \"method\":\"" + METHOD_ETH_GetBalance + "\", \"params\":[\"" + wallet + "\", \"latest\"] }"); + } catch (JSONException e) { + e.printStackTrace(); + request.error = e.toString(); + } + return request; + } + + public static Infura_Request GetTokenBalance(String wallet, String contract, int dec) + { + Infura_Request request = new Infura_Request(); + try { + request.WalletAddress=wallet; + request.Dec = dec; + String address = wallet.substring(2); + String dataValue = String.format("{\"data\": \"0x70a08231000000000000000000000000%s\", \"to\": \"%s\"}", address, contract); + request.jsRequestData = new JSONObject("{ \"method\":\"" + METHOD_ETH_Call + "\", \"params\":[" +dataValue+", \"latest\"] }"); + } catch (JSONException e) { + e.printStackTrace(); + request.error = e.toString(); + } + return request; + } + + public static Infura_Request SendTransaction(String wallet, String tx) { + Infura_Request request = new Infura_Request(); + try { + request.WalletAddress=wallet; + request.jsRequestData = new JSONObject("{ \"method\":\"" + METHOD_ETH_SendRawTransaction + "\", \"params\":[\"" + tx + "\"] }"); + } catch (JSONException e) { + e.printStackTrace(); + request.error = e.toString(); + } + return request; + } + + + + public static Infura_Request GetOutTransactionCount(String wallet) { + Infura_Request request = new Infura_Request(); + try { + request.WalletAddress=wallet; + request.jsRequestData = new JSONObject("{ \"method\":\"" + METHOD_ETH_GetOutTransactionCount + "\", \"params\":[\"" + wallet + "\", \"latest\"] }"); + } catch (JSONException e) { + e.printStackTrace(); + request.error = e.toString(); + } + return request; + } + + public static Infura_Request GetPendingTransactionCount(String wallet) { + Infura_Request request = new Infura_Request(); + try { + request.WalletAddress=wallet; + request.jsRequestData = new JSONObject("{ \"method\":\"" + METHOD_ETH_GetOutTransactionCount + "\", \"params\":[\"" + wallet + "\", \"pending\"] }"); + } catch (JSONException e) { + e.printStackTrace(); + request.error = e.toString(); + } + return request; + } + + public static Infura_Request GetGasPrise(String wallet) { + Infura_Request request = new Infura_Request(); + try { + request.WalletAddress=wallet; + request.jsRequestData = new JSONObject("{ \"method\":\"" + METHOD_ETH_GetGasPrice + "\", \"params\":[] }"); + } catch (JSONException e) { + e.printStackTrace(); + request.error = e.toString(); + } + return request; + } + + + public static Infura_Request SendTransactionCount(String wallet, String TX) { + Infura_Request request = new Infura_Request(); + try { + request.WalletAddress=wallet; + request.jsRequestData = new JSONObject("{ \"method\":\"" + METHOD_ETH_SendRawTransaction + "\", \"params\":[\"" + TX + "\"] }"); + } catch (JSONException e) { + e.printStackTrace(); + request.error = e.toString(); + } + return request; + } + + + + //METHOD_ETH_GetOutTransactionCount + + + public JSONArray getParams() throws JSONException { + return jsRequestData.getJSONArray("params"); + } + + public JSONObject getResult() throws JSONException { + return getAnswer().getJSONObject("result"); + } + + public String getResultString() throws JSONException { + return getAnswer().getString("result"); + } + + public JSONArray getResultArray() throws JSONException { + return getAnswer().getJSONArray("result"); + } +} + diff --git a/app/src/main/java/com/tangem/wallet/Infura_Task.java b/app/src/main/java/com/tangem/wallet/Infura_Task.java new file mode 100644 index 0000000000..7e0ded0162 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/Infura_Task.java @@ -0,0 +1,134 @@ +package com.tangem.wallet; + +/** + * Created by Ilia on 19.12.2017. + */ + +import android.os.AsyncTask; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.ArrayList; +import java.util.List; + +import javax.net.ssl.HttpsURLConnection; + +/** + * Created by Ilia on 04.12.2017. + */ + +public class Infura_Task extends AsyncTask> { + private Exception exception; + private Blockchain blockchain; + public Infura_Task(Blockchain blockchainNet) + { + blockchain = blockchainNet; + } + boolean useOurNode = false; + protected List doInBackground(Infura_Request... requests) { + List result = new ArrayList<>(); + for (int i = 0; i < requests.length; i++) { + result.add(requests[i]); + } + + for (Infura_Request request: result) + { + HttpURLConnection httpcon = null; + + try { + URL url = new URL("https://rinkeby.infura.io/AfWg0tmYEX5Kukn2UkKV"); + + if(blockchain == Blockchain.Ethereum || blockchain == Blockchain.Token){ + if(useOurNode) { + URL tmp = new URL("http://52.230.23.88"); + url = new URL(tmp.getProtocol(), tmp.getHost(), 27172, tmp.getFile()); + }else + url = new URL("https://mainnet.infura.io/AfWg0tmYEX5Kukn2UkKV"); + + } + + if(useOurNode) + { + httpcon = (HttpURLConnection)url.openConnection(); + } + else + { + httpcon = (HttpsURLConnection)url.openConnection(); + } + + if(httpcon == null) + { + request.error = String.format("Cann't connect to %s", url.getHost()); + return result; + } + + httpcon.setRequestMethod("POST"); + httpcon.setRequestProperty("Content-Type", "application/json"); + String params = request.getAsString(); + + OutputStream os = httpcon.getOutputStream(); + if(os == null) + { + request.error = String.format("Cann't recieve data from %s", url.getHost()); + return result; + } + BufferedWriter writer = new BufferedWriter( + new OutputStreamWriter(os, "UTF-8")); + if(writer == null) + { + request.error = String.format("Cann't send data to %s", url.getHost()); + + } + writer.write(params); + writer.flush(); + writer.close(); + os.close(); + + + httpcon.connect(); + request.getParams(); + System.out.println("code:"+httpcon.getResponseCode()); + int code = httpcon.getResponseCode(); + + BufferedReader in = new BufferedReader( + new InputStreamReader(httpcon.getInputStream())); + String inputLine; + StringBuffer response = new StringBuffer(); + + while ((inputLine = in.readLine()) != null) { + response.append(inputLine); + } + in.close(); + + request.answerData = response.toString(); + + } catch (Exception e) { + this.exception = e; + request.error = e.getMessage(); + } finally { + httpcon.disconnect(); + } + } + + return result; + } + + public String getValidationNodeDescription() { + if(blockchain == Blockchain.Ethereum || blockchain == Blockchain.Token) + { + if(useOurNode) + return "52.230.23.88:27172"; + else + return "Infura, infura.io"; + } + + + return "Infura, rinkeby.infura.io"; + } + +} diff --git a/app/src/main/java/com/tangem/wallet/Issuer.java b/app/src/main/java/com/tangem/wallet/Issuer.java new file mode 100644 index 0000000000..2c1a50ee73 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/Issuer.java @@ -0,0 +1,126 @@ +package com.tangem.wallet; + +import com.tangem.cardReader.CardCrypto; + +import org.spongycastle.jce.ECNamedCurveTable; +import org.spongycastle.jce.spec.ECNamedCurveParameterSpec; + +import java.math.BigInteger; +import java.util.Arrays; + +import static com.tangem.cardReader.CardCrypto.*; + +/** + * Created by dvol on 14.11.2017. + */ + +public enum Issuer { + Unknown("Unknown", "Unknown", null, null, null, null), + SMART_CASH_AG("SMART CASH AG", "SMART CASH AG", + IssuerKeyStorage.sdkPrivateDataKey, IssuerKeyStorage.GeneratePublicKey(IssuerKeyStorage.sdkPrivateDataKey), + IssuerKeyStorage.sdkPrivateTransactionKey, IssuerKeyStorage.GeneratePublicKey(IssuerKeyStorage.sdkPrivateTransactionKey)), + TANGEM_SDK("TANGEM SDK", "TANGEM SDK", + IssuerKeyStorage.sdkPrivateDataKey, IssuerKeyStorage.GeneratePublicKey(IssuerKeyStorage.sdkPrivateDataKey), + IssuerKeyStorage.sdkPrivateTransactionKey, IssuerKeyStorage.GeneratePublicKey(IssuerKeyStorage.sdkPrivateTransactionKey)), + TANGEM("TANGEM", "TANGEM", null, IssuerKeyStorage.tangemPublicDataKey, null, IssuerKeyStorage.tangemPublicTransactionKey) + ; + + + static class IssuerKeyStorage { + private static final byte[] sdkPrivateDataKey = new byte[]{ + (byte) 0x11, (byte) 0x12, (byte) 0x13, (byte) 0x14, (byte) 0x15, (byte) 0x16, (byte) 0x17, (byte) 0x18, + (byte) 0x47, (byte) 0x71, (byte) 0xED, (byte) 0x81, (byte) 0xF2, (byte) 0xBA, (byte) 0xCF, (byte) 0x57, + (byte) 0x47, (byte) 0x9E, (byte) 0x47, (byte) 0x35, (byte) 0xEB, (byte) 0x14, (byte) 0x05, (byte) 0x08, + (byte) 0x39, (byte) 0x27, (byte) 0x37, (byte) 0x2D, (byte) 0x40, (byte) 0xDA, (byte) 0x9E, (byte) 0x92}; + + private static final byte[] sdkPrivateTransactionKey = new byte[]{ + (byte) 0x11, (byte) 0x12, (byte) 0x13, (byte) 0x14, (byte) 0x15, (byte) 0x16, (byte) 0x17, (byte) 0x18, + (byte) 0x47, (byte) 0x71, (byte) 0xED, (byte) 0x81, (byte) 0xF2, (byte) 0xBA, (byte) 0xCF, (byte) 0x57, + (byte) 0x47, (byte) 0x9E, (byte) 0x47, (byte) 0x35, (byte) 0xEB, (byte) 0x14, (byte) 0x05, (byte) 0x08, + (byte) 0x19, (byte) 0x18, (byte) 0x17, (byte) 0x16, (byte) 0x15, (byte) 0x14, (byte) 0x13, (byte) 0x12}; + + private static byte[] tangemPublicDataKey = { + (byte) 0x04 , + (byte) 0x81 ,(byte) 0x96 ,(byte) 0xAA ,(byte) 0x4B ,(byte) 0x41 ,(byte) 0x0A ,(byte) 0xC4 ,(byte) 0x4A, + (byte) 0x3B ,(byte) 0x9C ,(byte) 0xCE ,(byte) 0x18 ,(byte) 0xE7 ,(byte) 0xBE ,(byte) 0x22 ,(byte) 0x6A, + (byte) 0xEA ,(byte) 0x07 ,(byte) 0x0A ,(byte) 0xCC ,(byte) 0x83 ,(byte) 0xA9 ,(byte) 0xCF ,(byte) 0x67, + (byte) 0x54 ,(byte) 0x0F ,(byte) 0xAC ,(byte) 0x49 ,(byte) 0xAF ,(byte) 0x25 ,(byte) 0x12 ,(byte) 0x9F, + (byte) 0x6A ,(byte) 0x53 ,(byte) 0x8A ,(byte) 0x28 ,(byte) 0xAD ,(byte) 0x63 ,(byte) 0x41 ,(byte) 0x35, + (byte) 0x8E ,(byte) 0x3C ,(byte) 0x4F ,(byte) 0x99 ,(byte) 0x63 ,(byte) 0x06 ,(byte) 0x4F ,(byte) 0x7E, + (byte) 0x36 ,(byte) 0x53 ,(byte) 0x72 ,(byte) 0xA6 ,(byte) 0x51 ,(byte) 0xD3 ,(byte) 0x74 ,(byte) 0xE5, + (byte) 0xC2 ,(byte) 0x3C ,(byte) 0xDD ,(byte) 0x37 ,(byte) 0xFD ,(byte) 0x09 ,(byte) 0x9B ,(byte) 0xF2}; + + private static byte[] tangemPublicTransactionKey = { + (byte) 0x04 , + (byte) 0x34 ,(byte) 0x3D ,(byte) 0x40 ,(byte) 0x49 ,(byte) 0x6C ,(byte) 0xBE ,(byte) 0x1F ,(byte) 0xE8, + (byte) 0xA8 ,(byte) 0xC0 ,(byte) 0x26 ,(byte) 0x57 ,(byte) 0x5C ,(byte) 0x43 ,(byte) 0x5A ,(byte) 0x29, + (byte) 0x14 ,(byte) 0x1E ,(byte) 0xA3 ,(byte) 0xBC ,(byte) 0x33 ,(byte) 0x5D ,(byte) 0xA5 ,(byte) 0x54, + (byte) 0x9A ,(byte) 0xB6 ,(byte) 0xC6 ,(byte) 0x46 ,(byte) 0x85 ,(byte) 0xA6 ,(byte) 0x46 ,(byte) 0x84, + (byte) 0x80 ,(byte) 0x36 ,(byte) 0xD4 ,(byte) 0x81 ,(byte) 0xCF ,(byte) 0x9A ,(byte) 0x98 ,(byte) 0x93, + (byte) 0x90 ,(byte) 0xA8 ,(byte) 0xB0 ,(byte) 0x34 ,(byte) 0xB2 ,(byte) 0x29 ,(byte) 0xD9 ,(byte) 0x9B, + (byte) 0xD4 ,(byte) 0x9E ,(byte) 0x6F ,(byte) 0x07 ,(byte) 0xD2 ,(byte) 0xFF ,(byte) 0x02 ,(byte) 0x74, + (byte) 0x6E ,(byte) 0xA2 ,(byte) 0x65 ,(byte) 0xEF ,(byte) 0x99 ,(byte) 0x38 ,(byte) 0x0A ,(byte) 0x80}; + + public static byte[] GeneratePublicKey(byte[] privateKey) { + try { + return CardCrypto.GeneratePublicKey(privateKey); + } + catch (Exception e) + { + e.printStackTrace(); + return null; + } + } + } + + private String ID; + private String officialName; + private byte[] privateDataKeyArray; + private byte[] publicDataKeyArray; + private byte[] privateTransactionKeyArray; + private byte[] publicTransactionKeyArray; + + Issuer(String id, String officialName, byte[] privateDataKey, byte[] publicDataKey, byte[] privateTransactionKey, byte[] publicTransactionKey) { + this.ID = id; + this.officialName = officialName; + this.privateDataKeyArray = privateDataKey; + this.privateTransactionKeyArray = privateTransactionKey; + this.publicDataKeyArray = publicDataKey; + this.publicTransactionKeyArray = publicTransactionKey; + } + + public byte[] getPublicDataKey() { + return publicDataKeyArray; + } + + public byte[] getPublicTransactionKey() { + return publicTransactionKeyArray; + } + + public byte[] getPrivateDataKey() { + return privateDataKeyArray; + } + + public byte[] getPrivateTransactionKey() { + return privateTransactionKeyArray; + } + + public byte[] getID() { + return ID.getBytes(); + } + + public String getOfficialName() { + return officialName; + } + + public static Issuer FindIssuer(String ID, byte[] publicDataKey) { + Issuer[] issuers = Issuer.values(); + for (int i = 1; i < issuers.length; i++) { + if (issuers[i].ID.equals(ID) && Arrays.equals(issuers[i].getPublicDataKey(), publicDataKey)) { + return issuers[i]; + } + } + return Issuer.Unknown; + } + +} diff --git a/app/src/main/java/com/tangem/wallet/Keccak256.java b/app/src/main/java/com/tangem/wallet/Keccak256.java new file mode 100644 index 0000000000..bd8c2b5ded --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/Keccak256.java @@ -0,0 +1,39 @@ +package com.tangem.wallet; + +/** + * Created by Ilia on 18.12.2017. + */ + +public class Keccak256 extends KeccakCore { + + /** + * Create the engine. + */ + public Keccak256() + { + super("eth-keccak-256"); + } + + public Digest copy() + { + return copyState(new Keccak256()); + } + + public int engineGetDigestLength() + { + return 32; + } + + @Override + protected byte[] engineDigest() { + return null; + } + + @Override + protected void engineUpdate(byte arg0) { + } + + @Override + protected void engineUpdate(byte[] arg0, int arg1, int arg2) { + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/wallet/KeccakCore.java b/app/src/main/java/com/tangem/wallet/KeccakCore.java new file mode 100644 index 0000000000..4eafa6de60 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/KeccakCore.java @@ -0,0 +1,546 @@ +package com.tangem.wallet; + + +/** + * Created by Ilia on 18.12.2017. + */ +abstract class KeccakCore extends DigestEngine{ + KeccakCore(String alg) + { + super(alg); + } + + private long[] A; + private byte[] tmpOut; + + private static final long[] RC = { + 0x0000000000000001L, 0x0000000000008082L, + 0x800000000000808AL, 0x8000000080008000L, + 0x000000000000808BL, 0x0000000080000001L, + 0x8000000080008081L, 0x8000000000008009L, + 0x000000000000008AL, 0x0000000000000088L, + 0x0000000080008009L, 0x000000008000000AL, + 0x000000008000808BL, 0x800000000000008BL, + 0x8000000000008089L, 0x8000000000008003L, + 0x8000000000008002L, 0x8000000000000080L, + 0x000000000000800AL, 0x800000008000000AL, + 0x8000000080008081L, 0x8000000000008080L, + 0x0000000080000001L, 0x8000000080008008L + }; + + /** + * Encode the 64-bit word {@code val} into the array + * {@code buf} at offset {@code off}, in little-endian + * convention (least significant byte first). + * + * @param val the value to encode + * @param buf the destination buffer + * @param off the destination offset + */ + private static void encodeLELong(long val, byte[] buf, int off) + { + buf[off + 0] = (byte)val; + buf[off + 1] = (byte)(val >>> 8); + buf[off + 2] = (byte)(val >>> 16); + buf[off + 3] = (byte)(val >>> 24); + buf[off + 4] = (byte)(val >>> 32); + buf[off + 5] = (byte)(val >>> 40); + buf[off + 6] = (byte)(val >>> 48); + buf[off + 7] = (byte)(val >>> 56); + } + + /** + * Decode a 64-bit little-endian word from the array {@code buf} + * at offset {@code off}. + * + * @param buf the source buffer + * @param off the source offset + * @return the decoded value + */ + private static long decodeLELong(byte[] buf, int off) + { + return (buf[off + 0] & 0xFFL) + | ((buf[off + 1] & 0xFFL) << 8) + | ((buf[off + 2] & 0xFFL) << 16) + | ((buf[off + 3] & 0xFFL) << 24) + | ((buf[off + 4] & 0xFFL) << 32) + | ((buf[off + 5] & 0xFFL) << 40) + | ((buf[off + 6] & 0xFFL) << 48) + | ((buf[off + 7] & 0xFFL) << 56); + } + + protected void engineReset() + { + doReset(); + } + + protected void processBlock(byte[] data) + { + /* Input block */ + for (int i = 0; i < data.length; i += 8) + A[i >>> 3] ^= decodeLELong(data, i); + + long t0, t1, t2, t3, t4; + long tt0, tt1, tt2, tt3, tt4; + long t, kt; + long c0, c1, c2, c3, c4, bnn; + + /* + * Unrolling four rounds kills performance big time + * on Intel x86 Core2, in both 32-bit and 64-bit modes + * (less than 1 MB/s instead of 55 MB/s on x86-64). + * Unrolling two rounds appears to be fine. + */ + for (int j = 0; j < 24; j += 2) { + + tt0 = A[ 1] ^ A[ 6]; + tt1 = A[11] ^ A[16]; + tt0 ^= A[21] ^ tt1; + tt0 = (tt0 << 1) | (tt0 >>> 63); + tt2 = A[ 4] ^ A[ 9]; + tt3 = A[14] ^ A[19]; + tt0 ^= A[24]; + tt2 ^= tt3; + t0 = tt0 ^ tt2; + + tt0 = A[ 2] ^ A[ 7]; + tt1 = A[12] ^ A[17]; + tt0 ^= A[22] ^ tt1; + tt0 = (tt0 << 1) | (tt0 >>> 63); + tt2 = A[ 0] ^ A[ 5]; + tt3 = A[10] ^ A[15]; + tt0 ^= A[20]; + tt2 ^= tt3; + t1 = tt0 ^ tt2; + + tt0 = A[ 3] ^ A[ 8]; + tt1 = A[13] ^ A[18]; + tt0 ^= A[23] ^ tt1; + tt0 = (tt0 << 1) | (tt0 >>> 63); + tt2 = A[ 1] ^ A[ 6]; + tt3 = A[11] ^ A[16]; + tt0 ^= A[21]; + tt2 ^= tt3; + t2 = tt0 ^ tt2; + + tt0 = A[ 4] ^ A[ 9]; + tt1 = A[14] ^ A[19]; + tt0 ^= A[24] ^ tt1; + tt0 = (tt0 << 1) | (tt0 >>> 63); + tt2 = A[ 2] ^ A[ 7]; + tt3 = A[12] ^ A[17]; + tt0 ^= A[22]; + tt2 ^= tt3; + t3 = tt0 ^ tt2; + + tt0 = A[ 0] ^ A[ 5]; + tt1 = A[10] ^ A[15]; + tt0 ^= A[20] ^ tt1; + tt0 = (tt0 << 1) | (tt0 >>> 63); + tt2 = A[ 3] ^ A[ 8]; + tt3 = A[13] ^ A[18]; + tt0 ^= A[23]; + tt2 ^= tt3; + t4 = tt0 ^ tt2; + + A[ 0] = A[ 0] ^ t0; + A[ 5] = A[ 5] ^ t0; + A[10] = A[10] ^ t0; + A[15] = A[15] ^ t0; + A[20] = A[20] ^ t0; + A[ 1] = A[ 1] ^ t1; + A[ 6] = A[ 6] ^ t1; + A[11] = A[11] ^ t1; + A[16] = A[16] ^ t1; + A[21] = A[21] ^ t1; + A[ 2] = A[ 2] ^ t2; + A[ 7] = A[ 7] ^ t2; + A[12] = A[12] ^ t2; + A[17] = A[17] ^ t2; + A[22] = A[22] ^ t2; + A[ 3] = A[ 3] ^ t3; + A[ 8] = A[ 8] ^ t3; + A[13] = A[13] ^ t3; + A[18] = A[18] ^ t3; + A[23] = A[23] ^ t3; + A[ 4] = A[ 4] ^ t4; + A[ 9] = A[ 9] ^ t4; + A[14] = A[14] ^ t4; + A[19] = A[19] ^ t4; + A[24] = A[24] ^ t4; + A[ 5] = (A[ 5] << 36) | (A[ 5] >>> (64 - 36)); + A[10] = (A[10] << 3) | (A[10] >>> (64 - 3)); + A[15] = (A[15] << 41) | (A[15] >>> (64 - 41)); + A[20] = (A[20] << 18) | (A[20] >>> (64 - 18)); + A[ 1] = (A[ 1] << 1) | (A[ 1] >>> (64 - 1)); + A[ 6] = (A[ 6] << 44) | (A[ 6] >>> (64 - 44)); + A[11] = (A[11] << 10) | (A[11] >>> (64 - 10)); + A[16] = (A[16] << 45) | (A[16] >>> (64 - 45)); + A[21] = (A[21] << 2) | (A[21] >>> (64 - 2)); + A[ 2] = (A[ 2] << 62) | (A[ 2] >>> (64 - 62)); + A[ 7] = (A[ 7] << 6) | (A[ 7] >>> (64 - 6)); + A[12] = (A[12] << 43) | (A[12] >>> (64 - 43)); + A[17] = (A[17] << 15) | (A[17] >>> (64 - 15)); + A[22] = (A[22] << 61) | (A[22] >>> (64 - 61)); + A[ 3] = (A[ 3] << 28) | (A[ 3] >>> (64 - 28)); + A[ 8] = (A[ 8] << 55) | (A[ 8] >>> (64 - 55)); + A[13] = (A[13] << 25) | (A[13] >>> (64 - 25)); + A[18] = (A[18] << 21) | (A[18] >>> (64 - 21)); + A[23] = (A[23] << 56) | (A[23] >>> (64 - 56)); + A[ 4] = (A[ 4] << 27) | (A[ 4] >>> (64 - 27)); + A[ 9] = (A[ 9] << 20) | (A[ 9] >>> (64 - 20)); + A[14] = (A[14] << 39) | (A[14] >>> (64 - 39)); + A[19] = (A[19] << 8) | (A[19] >>> (64 - 8)); + A[24] = (A[24] << 14) | (A[24] >>> (64 - 14)); + bnn = ~A[12]; + kt = A[ 6] | A[12]; + c0 = A[ 0] ^ kt; + kt = bnn | A[18]; + c1 = A[ 6] ^ kt; + kt = A[18] & A[24]; + c2 = A[12] ^ kt; + kt = A[24] | A[ 0]; + c3 = A[18] ^ kt; + kt = A[ 0] & A[ 6]; + c4 = A[24] ^ kt; + A[ 0] = c0; + A[ 6] = c1; + A[12] = c2; + A[18] = c3; + A[24] = c4; + bnn = ~A[22]; + kt = A[ 9] | A[10]; + c0 = A[ 3] ^ kt; + kt = A[10] & A[16]; + c1 = A[ 9] ^ kt; + kt = A[16] | bnn; + c2 = A[10] ^ kt; + kt = A[22] | A[ 3]; + c3 = A[16] ^ kt; + kt = A[ 3] & A[ 9]; + c4 = A[22] ^ kt; + A[ 3] = c0; + A[ 9] = c1; + A[10] = c2; + A[16] = c3; + A[22] = c4; + bnn = ~A[19]; + kt = A[ 7] | A[13]; + c0 = A[ 1] ^ kt; + kt = A[13] & A[19]; + c1 = A[ 7] ^ kt; + kt = bnn & A[20]; + c2 = A[13] ^ kt; + kt = A[20] | A[ 1]; + c3 = bnn ^ kt; + kt = A[ 1] & A[ 7]; + c4 = A[20] ^ kt; + A[ 1] = c0; + A[ 7] = c1; + A[13] = c2; + A[19] = c3; + A[20] = c4; + bnn = ~A[17]; + kt = A[ 5] & A[11]; + c0 = A[ 4] ^ kt; + kt = A[11] | A[17]; + c1 = A[ 5] ^ kt; + kt = bnn | A[23]; + c2 = A[11] ^ kt; + kt = A[23] & A[ 4]; + c3 = bnn ^ kt; + kt = A[ 4] | A[ 5]; + c4 = A[23] ^ kt; + A[ 4] = c0; + A[ 5] = c1; + A[11] = c2; + A[17] = c3; + A[23] = c4; + bnn = ~A[ 8]; + kt = bnn & A[14]; + c0 = A[ 2] ^ kt; + kt = A[14] | A[15]; + c1 = bnn ^ kt; + kt = A[15] & A[21]; + c2 = A[14] ^ kt; + kt = A[21] | A[ 2]; + c3 = A[15] ^ kt; + kt = A[ 2] & A[ 8]; + c4 = A[21] ^ kt; + A[ 2] = c0; + A[ 8] = c1; + A[14] = c2; + A[15] = c3; + A[21] = c4; + A[ 0] = A[ 0] ^ RC[j + 0]; + + tt0 = A[ 6] ^ A[ 9]; + tt1 = A[ 7] ^ A[ 5]; + tt0 ^= A[ 8] ^ tt1; + tt0 = (tt0 << 1) | (tt0 >>> 63); + tt2 = A[24] ^ A[22]; + tt3 = A[20] ^ A[23]; + tt0 ^= A[21]; + tt2 ^= tt3; + t0 = tt0 ^ tt2; + + tt0 = A[12] ^ A[10]; + tt1 = A[13] ^ A[11]; + tt0 ^= A[14] ^ tt1; + tt0 = (tt0 << 1) | (tt0 >>> 63); + tt2 = A[ 0] ^ A[ 3]; + tt3 = A[ 1] ^ A[ 4]; + tt0 ^= A[ 2]; + tt2 ^= tt3; + t1 = tt0 ^ tt2; + + tt0 = A[18] ^ A[16]; + tt1 = A[19] ^ A[17]; + tt0 ^= A[15] ^ tt1; + tt0 = (tt0 << 1) | (tt0 >>> 63); + tt2 = A[ 6] ^ A[ 9]; + tt3 = A[ 7] ^ A[ 5]; + tt0 ^= A[ 8]; + tt2 ^= tt3; + t2 = tt0 ^ tt2; + + tt0 = A[24] ^ A[22]; + tt1 = A[20] ^ A[23]; + tt0 ^= A[21] ^ tt1; + tt0 = (tt0 << 1) | (tt0 >>> 63); + tt2 = A[12] ^ A[10]; + tt3 = A[13] ^ A[11]; + tt0 ^= A[14]; + tt2 ^= tt3; + t3 = tt0 ^ tt2; + + tt0 = A[ 0] ^ A[ 3]; + tt1 = A[ 1] ^ A[ 4]; + tt0 ^= A[ 2] ^ tt1; + tt0 = (tt0 << 1) | (tt0 >>> 63); + tt2 = A[18] ^ A[16]; + tt3 = A[19] ^ A[17]; + tt0 ^= A[15]; + tt2 ^= tt3; + t4 = tt0 ^ tt2; + + A[ 0] = A[ 0] ^ t0; + A[ 3] = A[ 3] ^ t0; + A[ 1] = A[ 1] ^ t0; + A[ 4] = A[ 4] ^ t0; + A[ 2] = A[ 2] ^ t0; + A[ 6] = A[ 6] ^ t1; + A[ 9] = A[ 9] ^ t1; + A[ 7] = A[ 7] ^ t1; + A[ 5] = A[ 5] ^ t1; + A[ 8] = A[ 8] ^ t1; + A[12] = A[12] ^ t2; + A[10] = A[10] ^ t2; + A[13] = A[13] ^ t2; + A[11] = A[11] ^ t2; + A[14] = A[14] ^ t2; + A[18] = A[18] ^ t3; + A[16] = A[16] ^ t3; + A[19] = A[19] ^ t3; + A[17] = A[17] ^ t3; + A[15] = A[15] ^ t3; + A[24] = A[24] ^ t4; + A[22] = A[22] ^ t4; + A[20] = A[20] ^ t4; + A[23] = A[23] ^ t4; + A[21] = A[21] ^ t4; + A[ 3] = (A[ 3] << 36) | (A[ 3] >>> (64 - 36)); + A[ 1] = (A[ 1] << 3) | (A[ 1] >>> (64 - 3)); + A[ 4] = (A[ 4] << 41) | (A[ 4] >>> (64 - 41)); + A[ 2] = (A[ 2] << 18) | (A[ 2] >>> (64 - 18)); + A[ 6] = (A[ 6] << 1) | (A[ 6] >>> (64 - 1)); + A[ 9] = (A[ 9] << 44) | (A[ 9] >>> (64 - 44)); + A[ 7] = (A[ 7] << 10) | (A[ 7] >>> (64 - 10)); + A[ 5] = (A[ 5] << 45) | (A[ 5] >>> (64 - 45)); + A[ 8] = (A[ 8] << 2) | (A[ 8] >>> (64 - 2)); + A[12] = (A[12] << 62) | (A[12] >>> (64 - 62)); + A[10] = (A[10] << 6) | (A[10] >>> (64 - 6)); + A[13] = (A[13] << 43) | (A[13] >>> (64 - 43)); + A[11] = (A[11] << 15) | (A[11] >>> (64 - 15)); + A[14] = (A[14] << 61) | (A[14] >>> (64 - 61)); + A[18] = (A[18] << 28) | (A[18] >>> (64 - 28)); + A[16] = (A[16] << 55) | (A[16] >>> (64 - 55)); + A[19] = (A[19] << 25) | (A[19] >>> (64 - 25)); + A[17] = (A[17] << 21) | (A[17] >>> (64 - 21)); + A[15] = (A[15] << 56) | (A[15] >>> (64 - 56)); + A[24] = (A[24] << 27) | (A[24] >>> (64 - 27)); + A[22] = (A[22] << 20) | (A[22] >>> (64 - 20)); + A[20] = (A[20] << 39) | (A[20] >>> (64 - 39)); + A[23] = (A[23] << 8) | (A[23] >>> (64 - 8)); + A[21] = (A[21] << 14) | (A[21] >>> (64 - 14)); + bnn = ~A[13]; + kt = A[ 9] | A[13]; + c0 = A[ 0] ^ kt; + kt = bnn | A[17]; + c1 = A[ 9] ^ kt; + kt = A[17] & A[21]; + c2 = A[13] ^ kt; + kt = A[21] | A[ 0]; + c3 = A[17] ^ kt; + kt = A[ 0] & A[ 9]; + c4 = A[21] ^ kt; + A[ 0] = c0; + A[ 9] = c1; + A[13] = c2; + A[17] = c3; + A[21] = c4; + bnn = ~A[14]; + kt = A[22] | A[ 1]; + c0 = A[18] ^ kt; + kt = A[ 1] & A[ 5]; + c1 = A[22] ^ kt; + kt = A[ 5] | bnn; + c2 = A[ 1] ^ kt; + kt = A[14] | A[18]; + c3 = A[ 5] ^ kt; + kt = A[18] & A[22]; + c4 = A[14] ^ kt; + A[18] = c0; + A[22] = c1; + A[ 1] = c2; + A[ 5] = c3; + A[14] = c4; + bnn = ~A[23]; + kt = A[10] | A[19]; + c0 = A[ 6] ^ kt; + kt = A[19] & A[23]; + c1 = A[10] ^ kt; + kt = bnn & A[ 2]; + c2 = A[19] ^ kt; + kt = A[ 2] | A[ 6]; + c3 = bnn ^ kt; + kt = A[ 6] & A[10]; + c4 = A[ 2] ^ kt; + A[ 6] = c0; + A[10] = c1; + A[19] = c2; + A[23] = c3; + A[ 2] = c4; + bnn = ~A[11]; + kt = A[ 3] & A[ 7]; + c0 = A[24] ^ kt; + kt = A[ 7] | A[11]; + c1 = A[ 3] ^ kt; + kt = bnn | A[15]; + c2 = A[ 7] ^ kt; + kt = A[15] & A[24]; + c3 = bnn ^ kt; + kt = A[24] | A[ 3]; + c4 = A[15] ^ kt; + A[24] = c0; + A[ 3] = c1; + A[ 7] = c2; + A[11] = c3; + A[15] = c4; + bnn = ~A[16]; + kt = bnn & A[20]; + c0 = A[12] ^ kt; + kt = A[20] | A[ 4]; + c1 = bnn ^ kt; + kt = A[ 4] & A[ 8]; + c2 = A[20] ^ kt; + kt = A[ 8] | A[12]; + c3 = A[ 4] ^ kt; + kt = A[12] & A[16]; + c4 = A[ 8] ^ kt; + A[12] = c0; + A[16] = c1; + A[20] = c2; + A[ 4] = c3; + A[ 8] = c4; + A[ 0] = A[ 0] ^ RC[j + 1]; + t = A[ 5]; + A[ 5] = A[18]; + A[18] = A[11]; + A[11] = A[10]; + A[10] = A[ 6]; + A[ 6] = A[22]; + A[22] = A[20]; + A[20] = A[12]; + A[12] = A[19]; + A[19] = A[15]; + A[15] = A[24]; + A[24] = A[ 8]; + A[ 8] = t; + t = A[ 1]; + A[ 1] = A[ 9]; + A[ 9] = A[14]; + A[14] = A[ 2]; + A[ 2] = A[13]; + A[13] = A[23]; + A[23] = A[ 4]; + A[ 4] = A[21]; + A[21] = A[16]; + A[16] = A[ 3]; + A[ 3] = A[17]; + A[17] = A[ 7]; + A[ 7] = t; + } + } + + protected void doPadding(byte[] out, int off) + { + int ptr = flush(); + byte[] buf = getBlockBuffer(); + if ((ptr + 1) == buf.length) { + buf[ptr] = (byte)0x81; + } else { + buf[ptr] = (byte)0x01; + for (int i = ptr + 1; i < (buf.length - 1); i ++) + buf[i] = 0; + buf[buf.length - 1] = (byte)0x80; + } + processBlock(buf); + A[ 1] = ~A[ 1]; + A[ 2] = ~A[ 2]; + A[ 8] = ~A[ 8]; + A[12] = ~A[12]; + A[17] = ~A[17]; + A[20] = ~A[20]; + int dlen = engineGetDigestLength(); + for (int i = 0; i < dlen; i += 8) + encodeLELong(A[i >>> 3], tmpOut, i); + System.arraycopy(tmpOut, 0, out, off, dlen); + } + + protected void doInit() + { + A = new long[25]; + tmpOut = new byte[(engineGetDigestLength() + 7) & ~7]; + doReset(); + } + + public int getBlockLength() + { + return 200 - 2 * engineGetDigestLength(); + } + + private final void doReset() + { + for (int i = 0; i < 25; i ++) + A[i] = 0; + A[ 1] = 0xFFFFFFFFFFFFFFFFL; + A[ 2] = 0xFFFFFFFFFFFFFFFFL; + A[ 8] = 0xFFFFFFFFFFFFFFFFL; + A[12] = 0xFFFFFFFFFFFFFFFFL; + A[17] = 0xFFFFFFFFFFFFFFFFL; + A[20] = 0xFFFFFFFFFFFFFFFFL; + } + + + protected Digest copyState(KeccakCore dst) + { + System.arraycopy(A, 0, dst.A, 0, 25); + return super.copyState(dst); + } + + public String toString() + { + return "Keccak-" + (engineGetDigestLength() << 3); + } +} diff --git a/app/src/main/java/com/tangem/wallet/LastSignStorage.java b/app/src/main/java/com/tangem/wallet/LastSignStorage.java new file mode 100644 index 0000000000..73dc61b561 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/LastSignStorage.java @@ -0,0 +1,146 @@ +package com.tangem.wallet; + +import android.content.Context; +import android.content.SharedPreferences; +import android.preference.PreferenceManager; +import android.util.ArrayMap; +import android.util.ArraySet; + +import java.lang.reflect.Array; +import java.util.Date; +import java.util.Map; +import java.util.Set; + +/** + * Created by dvol on 30.10.2017. + */ + +public class LastSignStorage { + + private static SharedPreferences sharedPreferences=null; + + private static Set cards = new ArraySet<>(); + private static Map dates = new ArrayMap<>(); + private static Map txCol = new ArrayMap<>(); + private static Map txCompleteCol = new ArrayMap<>(); + + static void Init(Context context) { + sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context); + cards=sharedPreferences.getStringSet("LastSign_Cards", cards); + for (int i = 0; i < cards.size(); i++) { + String wallet = cards.toArray()[i].toString(); + Date dt = new Date(); + dt.setTime(sharedPreferences.getLong("LastSign_" + wallet, 0)); + dates.put(wallet, dt); + } + } + + public static boolean needInit() { + return sharedPreferences==null; + } + + static class CompleteTx + { + public String TX; + public boolean isComplete; + } + public static Map GetTxList() + { + Set wallets=sharedPreferences.getStringSet("LastSign_Cards", cards); + Map txList = new ArrayMap<>(); + + for (int i = 0; i < wallets.size(); i++) { + String wallet = wallets.toArray()[i].toString(); + String tx = sharedPreferences.getString("LastSignTX_" + wallet, ""); + boolean complete = sharedPreferences.getBoolean("LastSignTXComplete_" + wallet, false); + CompleteTx txComplete = new CompleteTx(); + txComplete.isComplete = complete; + txComplete.TX = tx; + txList.put(wallet, txComplete); + } + + return txList; + } + public static Date getLastSignDate(String wallet) { + if (dates.containsKey(wallet)) return dates.get(wallet); + return null; + } + + public static void setLastSignDate(String wallet, Date date) { + SharedPreferences.Editor editor = sharedPreferences.edit(); + if (!cards.contains(wallet)) { + cards.add(wallet); + editor.putStringSet("LastSign_Cards", cards); + } + dates.put(wallet, date); + editor.putLong("LastSign_" + wallet, date.getTime()); + editor.apply(); + } + + public static void setLastTX(String wallet, String tx) { + SharedPreferences.Editor editor = sharedPreferences.edit(); + if (!cards.contains(wallet)) { + cards.add(wallet); + editor.putStringSet("LastSign_Cards", cards); + } + + editor.putString("LastSignTX_" + wallet, tx); + editor.putBoolean("LastSignTXComplete_" + wallet, false); + editor.apply(); + } + + public static void setLastMessage(String wallet, String message) { + SharedPreferences.Editor editor = sharedPreferences.edit(); + if (!cards.contains(wallet)) { + cards.add(wallet); + editor.putStringSet("LastSign_Cards", cards); + } + + editor.putString("LastSignMessage_" + wallet, message); + editor.apply(); + } + + public static String getLastMessage(String wallet) + { + try { + String msg = sharedPreferences.getString("LastSignMessage_" + wallet, ""); + return msg; + } + catch(Exception e) + { + return ""; + } + } + + public static void setTxWasSend(String wallet) + { + + Map txList = GetTxList(); + if(txList.containsKey(wallet)) + { + SharedPreferences.Editor editor = sharedPreferences.edit(); + editor.putBoolean("LastSignTXComplete_" + wallet, true); + editor.apply(); + } + } + public static boolean getNeedTxSend(String wallet) + { + Map txList = GetTxList(); + if(txList.containsKey(wallet)) + { + boolean complete = txList.get(wallet).isComplete; + return !complete; + } + return false; + } + + public static String getTxForSend(String wallet) + { + Map txList = GetTxList(); + if(txList.containsKey(wallet)) + { + return txList.get(wallet).TX; + } + return ""; + } +} diff --git a/app/src/main/java/com/tangem/wallet/LoadedWalletActivity.java b/app/src/main/java/com/tangem/wallet/LoadedWalletActivity.java new file mode 100644 index 0000000000..9408cb900e --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/LoadedWalletActivity.java @@ -0,0 +1,37 @@ +package com.tangem.wallet; + +import android.app.Activity; +import android.content.Intent; +import android.nfc.NfcAdapter; +import android.nfc.Tag; +import android.os.Bundle; +import android.support.v7.app.AppCompatActivity; + + +public class LoadedWalletActivity extends AppCompatActivity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_loaded_wallet); + MainActivity.commonInit(getApplicationContext()); + + if( getIntent().getExtras().containsKey(NfcAdapter.EXTRA_TAG) ) + { + Tag tag=getIntent().getParcelableExtra(NfcAdapter.EXTRA_TAG); + if (tag != null ) { + LoadedWalletActivityFragment fragment=(LoadedWalletActivityFragment)(getSupportFragmentManager().findFragmentById(R.id.loaded_wallet_fragment)); + fragment.onTagDiscovered(tag); + } + } + } + + @Override + public void onBackPressed() { + LoadedWalletActivityFragment loadedWalletActivityFragment=(LoadedWalletActivityFragment) getSupportFragmentManager().findFragmentById(R.id.loaded_wallet_fragment); + Intent data= loadedWalletActivityFragment.prepareResultIntent(); + data.putExtra("modification", "update"); + setResult(Activity.RESULT_OK, data); + finish(); + } +} diff --git a/app/src/main/java/com/tangem/wallet/LoadedWalletActivityFragment.java b/app/src/main/java/com/tangem/wallet/LoadedWalletActivityFragment.java new file mode 100644 index 0000000000..e001ac92af --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/LoadedWalletActivityFragment.java @@ -0,0 +1,1599 @@ +package com.tangem.wallet; + +import android.app.Activity; +import android.app.AlertDialog; +import android.app.Dialog; +import android.app.DialogFragment; +import android.content.ClipData; +import android.content.ClipboardManager; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.res.ColorStateList; +import android.graphics.Bitmap; +import android.graphics.Color; +import android.nfc.NfcAdapter; +import android.nfc.Tag; +import android.nfc.tech.IsoDep; +import android.os.AsyncTask; +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.support.v4.widget.SwipeRefreshLayout; +import android.text.Html; +import android.text.Spanned; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.PopupMenu; +import android.widget.ProgressBar; +import android.widget.TextView; +import android.widget.Toast; + +import com.google.zxing.BarcodeFormat; +import com.google.zxing.EncodeHintType; +import com.google.zxing.WriterException; +import com.google.zxing.common.BitMatrix; +import com.google.zxing.qrcode.QRCodeWriter; +import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel; +import com.tangem.cardReader.CardProtocol; +import com.tangem.cardReader.NfcManager; +import com.tangem.cardReader.Util; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.Hashtable; +import java.util.List; +import java.util.Timer; +import java.util.TimerTask; + +import static android.content.Context.CLIPBOARD_SERVICE; + +/** + * A placeholder fragment containing a simple view. + */ +public class LoadedWalletActivityFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener, NfcAdapter.ReaderCallback, CardProtocol.Notifications { + + + private static final int REQUEST_CODE_SEND_PAYMENT = 1; + private static final int REQUEST_CODE_PURGE = 2; + private static final int REQUEST_CODE_REQUEST_PIN2_FOR_PURGE = 3; + private static final int REQUEST_CODE_VERIFY_CARD = 4; + private static final int REQUEST_CODE_ENTER_NEW_PIN = 5; + private static final int REQUEST_CODE_ENTER_NEW_PIN2 = 6; + private static final int REQUEST_CODE_REQUEST_PIN2_FOR_SWAP_PIN = 7; + private static final int REQUEST_CODE_SWAP_PIN = 8; + Tangem_Card mCard; + TextView tvCardID, tvBalance, tvOffline, tvBalanceEquivalent, tvWallet, tvInputs, lbInputs, tvOutputs, tvSend, tvPurge, tvError, tvMessage, tvIssuer, tvIssuerData, tvBlockchain, tvLastInput, tvLastOutput, lbLastOutput, tvValidationNode; + TextView tvHeader, tvCaution; + ImageView imgLookup; + TextView tvLookup; + ProgressBar progressBar; + ImageView ivBlockchain, ivPIN, ivPIN2orSecurityDelay, ivDeveloperVersion, ivQR; + SwipeRefreshLayout mSwipeRefreshLayout; + List updateTasks = new ArrayList<>(); + private NfcManager mNfcManager; + private final String logTag = "LoadedWalletFragment"; + private boolean lastReadSuccess = true; + private VerifyCardTask verifyCardTask = null; + private int requestPIN2Count = 0; + + public LoadedWalletActivityFragment() { + + } + + public void onRefresh() { + //Update + // Showing refresh animation before making http call + if (updateTasks.size() > 0) return; + ; + mSwipeRefreshLayout.setRefreshing(true); + mCard.clearInfo(); + mCard.setError(null); + mCard.setMessage(null); + + boolean needResendTX = LastSignStorage.getNeedTxSend(mCard.getWallet()); + + UpdateViews(); + + CoinEngine engine = CoinEngineFactory.Create(mCard.getBlockchain()); + + if (mCard.getBlockchain() == Blockchain.Bitcoin || mCard.getBlockchain() == Blockchain.BitcoinTestNet) { + SharedData data = new SharedData(SharedData.COUNT_REQUEST); + for (int i = 0; i < data.allRequest; ++i) { + String nodeAddress = engine.GetNextNode(mCard); + int nodePort = engine.GetNextNodePort(mCard); + UpdateWalletInfoTask connectTaskEx = new UpdateWalletInfoTask(nodeAddress, nodePort, data); + connectTaskEx.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, Electrum_Request.CheckBalance(mCard.getWallet())); + } + + String nodeAddress = engine.GetNode(mCard); + int nodePort = engine.GetNodePort(mCard); + UpdateWalletInfoTask updateWalletInfoTask = new UpdateWalletInfoTask(nodeAddress, nodePort, data); + updateTasks.add(updateWalletInfoTask); + updateWalletInfoTask.execute(Electrum_Request.ListUnspent(mCard.getWallet()), Electrum_Request.ListHistory(mCard.getWallet())); + + RateInfoTask taskRate = new RateInfoTask(); + ExchangeRequest rate = ExchangeRequest.GetRate(mCard.getWallet(), "bitcoin", "bitcoin"); + taskRate.execute(rate); + + + } else if (mCard.getBlockchain() == Blockchain.BitcoinCash || mCard.getBlockchain() == Blockchain.BitcoinCashTestNet) { + SharedData data = new SharedData(SharedData.COUNT_REQUEST); + for (int i = 0; i < data.allRequest; ++i) { + String nodeAddress = engine.GetNextNode(mCard); + int nodePort = engine.GetNextNodePort(mCard); + UpdateWalletInfoTask connectTaskEx = new UpdateWalletInfoTask(nodeAddress, nodePort, data); + connectTaskEx.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, Electrum_Request.CheckBalance(mCard.getWallet())); + } + + String nodeAddress = engine.GetNode(mCard); + int nodePort = engine.GetNodePort(mCard); + UpdateWalletInfoTask updateWalletInfoTask = new UpdateWalletInfoTask(nodeAddress, nodePort, data); + + updateTasks.add(updateWalletInfoTask); + updateWalletInfoTask.execute(Electrum_Request.ListUnspent(mCard.getWallet()), Electrum_Request.ListHistory(mCard.getWallet())); + + RateInfoTask taskRate = new RateInfoTask(); + ExchangeRequest rate = ExchangeRequest.GetRate(mCard.getWallet(), "bitcoin-cash", "bitcoin-cash"); + taskRate.execute(rate); + + + } + else if (mCard.getBlockchain() == Blockchain.Ethereum || mCard.getBlockchain() == Blockchain.EthereumTestNet) { + ETHRequestTask updateETH = new ETHRequestTask(mCard.getBlockchain()); + Infura_Request reqETH = Infura_Request.GetBalance(mCard.getWallet()); + reqETH.setID(67); + reqETH.setBlockchain(mCard.getBlockchain()); + + Infura_Request reqNonce = Infura_Request.GetOutTransactionCount(mCard.getWallet()); + reqNonce.setID(67); + reqNonce.setBlockchain(mCard.getBlockchain()); + + updateETH.execute(reqETH, reqNonce); + + + RateInfoTask taskRate = new RateInfoTask(); + ExchangeRequest rate = ExchangeRequest.GetRate(mCard.getWallet(), "ethereum", "ethereum"); + taskRate.execute(rate); + + } else if (mCard.getBlockchain() == Blockchain.Token) { + ETHRequestTask updateETH = new ETHRequestTask(mCard.getBlockchain()); + Infura_Request reqETH = Infura_Request.GetTokenBalance(mCard.getWallet(), engine.GetContractAddress(mCard), engine.GetTokenDecimals(mCard)); + reqETH.setID(67); + reqETH.setBlockchain(mCard.getBlockchain()); + + Infura_Request reqBalance = Infura_Request.GetBalance(mCard.getWallet()); + reqBalance.setID(67); + reqBalance.setBlockchain(mCard.getBlockchain()); + + Infura_Request reqNonce = Infura_Request.GetOutTransactionCount(mCard.getWallet()); + reqNonce.setID(67); + reqNonce.setBlockchain(mCard.getBlockchain()); + updateETH.execute(reqETH, reqNonce, reqBalance); + + + RateInfoTask taskRate = new RateInfoTask(); + ExchangeRequest rate = ExchangeRequest.GetRate(mCard.getWallet(), "basic-attention-token", "ethereum"); + taskRate.execute(rate); + } + + if (needResendTX) { + SendTransaction(LastSignStorage.getTxForSend(mCard.getWallet())); + } + } + + void doShareWallet(boolean useURI) { + if (useURI) { + String txtShare = CoinEngineFactory.Create(mCard.getBlockchain()).getShareWalletURI(mCard).toString(); + //String txtShare = Blockchain.getShareWalletURI(mCard).toString(); + Intent intent = new Intent(Intent.ACTION_SEND); + intent.setType("text/plain"); + intent.putExtra(Intent.EXTRA_SUBJECT, "Wallet address"); + intent.putExtra(Intent.EXTRA_TEXT, txtShare); + + PackageManager packageManager = getActivity().getPackageManager(); + List activities = packageManager.queryIntentActivities(intent, PackageManager.MATCH_ALL); + boolean isIntentSafe = activities.size() > 0; + + if (isIntentSafe) { + String title = "Share wallet address with:"; + // Create intent to show chooser + Intent chooser = Intent.createChooser(intent, title); + // Verify the intent will resolve to at least one activity + if (intent.resolveActivity(getActivity().getPackageManager()) != null) { + startActivity(chooser); + } + } else { + ClipboardManager clipboard = (ClipboardManager) getActivity().getSystemService(CLIPBOARD_SERVICE); + clipboard.setPrimaryClip(ClipData.newPlainText(txtShare, txtShare)); + Toast.makeText(getContext(), "Copied to clipboard", Toast.LENGTH_LONG).show(); + } + } else { + String txtShare = mCard.getWallet(); + ClipboardManager clipboard = (ClipboardManager) getActivity().getSystemService(CLIPBOARD_SERVICE); + clipboard.setPrimaryClip(ClipData.newPlainText(txtShare, txtShare)); + Toast.makeText(getContext(), "Copied to clipboard", Toast.LENGTH_LONG).show(); + } + + + } + + @Override + public View onCreateView(final LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + View v = inflater.inflate(R.layout.fragment_loaded_wallet, container, false); + + mNfcManager = new NfcManager(this.getActivity(), this); + + + // SwipeRefreshLayout + mSwipeRefreshLayout = v.findViewById(R.id.swipe_container); + mSwipeRefreshLayout.setOnRefreshListener(this); + + mCard = new Tangem_Card(getActivity().getIntent().getStringExtra("UID")); + mCard.LoadFromBundle(getActivity().getIntent().getExtras().getBundle("Card")); + tvCardID = v.findViewById(R.id.tvCardID); + + tvBalance = v.findViewById(R.id.tvBalance); + tvOffline = v.findViewById(R.id.tvOffline); + + if (mCard.getBlockchain() == Blockchain.Token) { + //tvBalance.setLines(2); + tvBalance.setSingleLine(false); + } + + tvBalanceEquivalent = v.findViewById(R.id.tvBalanceEquivalent); + + tvWallet = v.findViewById(R.id.tvWallet); + tvWallet.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + doShareWallet(false); + } + }); + + tvInputs = v.findViewById(R.id.tvInputs); + lbInputs = v.findViewById(R.id.lbInputs); + + tvLastOutput = v.findViewById(R.id.tvLastOutput); + lbLastOutput = v.findViewById(R.id.lbLastOutput); + + final CoinEngine engine = CoinEngineFactory.Create(mCard.getBlockchain()); + + boolean visibleFlag = engine != null ? engine.InOutPutVisible() : true; + int visibleIOPuts = visibleFlag ? View.VISIBLE : View.GONE; + if (tvInputs != null) { + tvInputs.setVisibility(visibleIOPuts); + } + + if (lbInputs != null) { + lbInputs.setVisibility(visibleIOPuts); + } + + if (tvLastOutput != null) { + tvLastOutput.setVisibility(visibleIOPuts); + } + + if (lbLastOutput != null) { + lbLastOutput.setVisibility(visibleIOPuts); + } + + tvValidationNode = v.findViewById(R.id.tvValidationNode); + + progressBar = v.findViewById(R.id.progressBar); + + tvBlockchain = v.findViewById(R.id.tvBlockchain); + ivBlockchain = v.findViewById(R.id.imgBlockchain); + ivPIN = v.findViewById(R.id.imgPIN); + ivPIN2orSecurityDelay = v.findViewById(R.id.imgPIN2orSecurityDelay); + ivDeveloperVersion = v.findViewById(R.id.imgDeveloperVersion); + + ivQR = v.findViewById(R.id.qrWallet); + + + try { + ivQR.setImageBitmap(generateQrCode(engine.getShareWalletURI(mCard).toString())); + } catch (WriterException e) { + e.printStackTrace(); + } + ivQR.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + doShareWallet(true); + } + }); + + tvError = v.findViewById(R.id.tvError); + tvMessage = v.findViewById(R.id.tvMessage); + + tvIssuer = v.findViewById(R.id.tvIssuer); + tvIssuerData = v.findViewById(R.id.tvIssuerData); + + tvHeader = v.findViewById(R.id.tvHeader); + tvCaution = v.findViewById(R.id.tvCaution); + + tvSend = v.findViewById(R.id.tvSend); + + imgLookup = v.findViewById(R.id.imgLookup); + + if (imgLookup != null) { + + imgLookup.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (!mCard.hasBalanceInfo()) { + return; + } + CoinEngine engineClick = CoinEngineFactory.Create(mCard.getBlockchain()); + + Intent browserIntent = new Intent(Intent.ACTION_VIEW, engineClick.getShareWalletURIExplorer(mCard)); + startActivity(browserIntent); + } + }); + + } + + tvLookup = v.findViewById(R.id.tvLookup); + + if (tvLookup != null) { + + tvLookup.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (!mCard.hasBalanceInfo()) { + return; + } + CoinEngine engineClick = CoinEngineFactory.Create(mCard.getBlockchain()); + + Intent browserIntent = new Intent(Intent.ACTION_VIEW, engineClick.getShareWalletURIExplorer(mCard)); + startActivity(browserIntent); + } + }); + } + + if (tvSend != null) { + tvSend.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (!mCard.hasBalanceInfo()) { + return; + } else if (!engine.IsBalanceNotZero(mCard)) { + Toast.makeText(getContext(), "The wallet is empty", Toast.LENGTH_LONG).show(); + return; + } else if (!engine.IsBalanceAlterNotZero(mCard)) { + Toast.makeText(getContext(), "Not enough funds for transaction fee (gas)!", Toast.LENGTH_LONG).show(); + return; + } else if (engine.AwaitingConfirmation(mCard)) { + Toast.makeText(getContext(), "Please wait while previous transaction is confirmed in blockchain", Toast.LENGTH_LONG).show(); + return; + } else if (!engine.CheckUnspentTransaction(mCard)) { + Toast.makeText(getContext(), "Please wait for confirmation of incoming transaction!", Toast.LENGTH_LONG).show(); + return; + } else if (mCard.getRemainingSignatures() == 0) { + Toast.makeText(getContext(), "Card hasn't remaining signature!", Toast.LENGTH_LONG).show(); + return; + } + + Intent intent = new Intent(getContext(), PreparePaymentActivity.class); + intent.putExtra("UID", mCard.getUID()); + intent.putExtra("Card", mCard.getAsBundle()); + startActivityForResult(intent, REQUEST_CODE_SEND_PAYMENT); + } + }); + } + + tvPurge = v.findViewById(R.id.tvPurge); + if (tvPurge != null) { + tvPurge.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + showMenu(tvPurge); + } + } + ); + } + UpdateViews(); + + if (!mCard.hasBalanceInfo()) { + mSwipeRefreshLayout.setRefreshing(true); + mSwipeRefreshLayout.postDelayed(new Runnable() { + @Override + public void run() { + onRefresh(); + } + }, 1000); + } + return v; + } + + public void showMenu(View v) { + final PopupMenu popup = new PopupMenu(getActivity(), v); + MenuInflater inflater = popup.getMenuInflater(); + inflater.inflate(R.menu.menu_loaded_wallet, popup.getMenu()); + + popup.getMenu().findItem(R.id.action_set_PIN1).setVisible(mCard.allowSwapPIN()); + popup.getMenu().findItem(R.id.action_reset_PIN1).setVisible(mCard.allowSwapPIN() && !mCard.useDefaultPIN1()); + popup.getMenu().findItem(R.id.action_set_PIN2).setVisible(mCard.allowSwapPIN2()); + popup.getMenu().findItem(R.id.action_reset_PIN2).setVisible(mCard.allowSwapPIN2() && !mCard.useDefaultPIN2()); + popup.getMenu().findItem(R.id.action_reset_PINs).setVisible(mCard.allowSwapPIN() && mCard.allowSwapPIN2() && !mCard.useDefaultPIN1() && !mCard.useDefaultPIN2()); + + popup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() { + @Override + public boolean onMenuItemClick(MenuItem item) { + + int id = item.getItemId(); + switch (id) { + case R.id.action_set_PIN1: + doSetPin(); + return true; + case R.id.action_reset_PIN1: + doResetPin(); + return true; + case R.id.action_set_PIN2: + doSetPin2(); + return true; + case R.id.action_reset_PIN2: + doResetPin2(); + return true; + case R.id.action_reset_PINs: + doResetPins(); + return true; + case R.id.action_purge: + doPurge(); + return true; + default: + return false; + } + } + }); + popup.show(); + } + + String newPIN = "", newPIN2 = ""; + + void doSetPin() { + requestPIN2Count = 0; + Intent intent = new Intent(getContext(), RequestPINActivity.class); + intent.putExtra("mode", RequestPINActivity.Mode.RequestNewPIN.toString()); + newPIN = ""; + newPIN2 = ""; + startActivityForResult(intent, REQUEST_CODE_ENTER_NEW_PIN); + } + + void doResetPin() { + requestPIN2Count = 0; + Intent intent = new Intent(getContext(), RequestPINActivity.class); + intent.putExtra("mode", RequestPINActivity.Mode.RequestPIN2.toString()); + intent.putExtra("UID", mCard.getUID()); + intent.putExtra("Card", mCard.getAsBundle()); + newPIN = PINStorage.getDefaultPIN(); + newPIN2 = ""; + startActivityForResult(intent, REQUEST_CODE_REQUEST_PIN2_FOR_SWAP_PIN); + } + + void doResetPin2() { + requestPIN2Count = 0; + Intent intent = new Intent(getContext(), RequestPINActivity.class); + intent.putExtra("mode", RequestPINActivity.Mode.RequestPIN2.toString()); + intent.putExtra("UID", mCard.getUID()); + intent.putExtra("Card", mCard.getAsBundle()); + newPIN = ""; + newPIN2 = PINStorage.getDefaultPIN2(); + startActivityForResult(intent, REQUEST_CODE_REQUEST_PIN2_FOR_SWAP_PIN); + } + + void doResetPins() { + requestPIN2Count = 0; + Intent intent = new Intent(getContext(), RequestPINActivity.class); + intent.putExtra("mode", RequestPINActivity.Mode.RequestPIN2.toString()); + intent.putExtra("UID", mCard.getUID()); + intent.putExtra("Card", mCard.getAsBundle()); + newPIN = PINStorage.getDefaultPIN(); + newPIN2 = PINStorage.getDefaultPIN2(); + startActivityForResult(intent, REQUEST_CODE_REQUEST_PIN2_FOR_SWAP_PIN); + } + + + void doSetPin2() { + requestPIN2Count = 0; + Intent intent = new Intent(getContext(), RequestPINActivity.class); + intent.putExtra("mode", RequestPINActivity.Mode.RequestNewPIN2.toString()); + newPIN = ""; + newPIN2 = ""; + startActivityForResult(intent, REQUEST_CODE_ENTER_NEW_PIN2); + } + + void doPurge() { + requestPIN2Count = 0; + final CoinEngine engine = CoinEngineFactory.Create(mCard.getBlockchain()); + if (!mCard.hasBalanceInfo()) { + return; + } else if (engine.IsBalanceNotZero(mCard)) { + Toast.makeText(getContext(), "Cannot erase wallet with non-zero balance", Toast.LENGTH_LONG).show(); + return; + } + + Intent intent = new Intent(getContext(), RequestPINActivity.class); + intent.putExtra("mode", RequestPINActivity.Mode.RequestPIN2.toString()); + intent.putExtra("UID", mCard.getUID()); + intent.putExtra("Card", mCard.getAsBundle()); + startActivityForResult(intent, REQUEST_CODE_REQUEST_PIN2_FOR_PURGE); + } + + void UpdateViews() { + try { + if (timerHideErrorAndMessage != null) { + timerHideErrorAndMessage.cancel(); + timerHideErrorAndMessage = null; + } + tvCardID.setText(mCard.getCIDDescription()); + + if ((mCard.getError() == null || mCard.getError().isEmpty())) { + tvError.setVisibility(View.GONE); + tvError.setText(""); + } else { + tvError.setVisibility(View.VISIBLE); + tvError.setText(mCard.getError()); + } + + boolean needResendTX = LastSignStorage.getNeedTxSend(mCard.getWallet()); + + if ((mCard.getMessage() == null || mCard.getMessage().isEmpty()) && !needResendTX) { + tvMessage.setText(""); + tvMessage.setVisibility(View.GONE); + } else { + if (needResendTX) { + tvMessage.setText("Sending cached transaction..."); + } else { + tvMessage.setText(mCard.getMessage()); + } + tvMessage.setVisibility(View.VISIBLE); + + } + + + CoinEngine engine = CoinEngineFactory.Create(mCard.getBlockchain()); + + if (engine.HasBalanceInfo(mCard) || mCard.getOfflineBalance() == null) { + if (mCard.getBlockchain() == Blockchain.Token) { + Spanned html = Html.fromHtml(engine.GetBalanceWithAlter(mCard)); + tvBalance.setText(html); + } else { + tvBalance.setText(engine.GetBalanceWithAlter(mCard)); + } + + tvBalanceEquivalent.setText(engine.GetBalanceEquivalent(mCard)); + tvOffline.setVisibility(View.INVISIBLE); + } else { + String offlineAmount = engine.ConvertByteArrayToAmount(mCard, mCard.getOfflineBalance()); + if (mCard.getBlockchain() == Blockchain.Token) { + tvBalance.setText("NOT IMPLEMENTED"); + } else { + tvBalance.setText(engine.GetAmountDescription(mCard, offlineAmount)); + } + + tvBalanceEquivalent.setText(engine.GetAmountEqualentDescriptor(mCard, offlineAmount)); + tvOffline.setVisibility(View.VISIBLE); + } + + if (!mCard.getAmountEquivalentDescriptionAvailable()) { + //tvBalanceEquivalent.setError("Service unavailable"); + } else { + tvBalanceEquivalent.setError(null); + } + + tvWallet.setText(mCard.getWallet()); + + tvInputs.setText(mCard.getInputsDescription()); + if (mCard.getLastInputDescription().contains("awaiting")) { + tvInputs.setTextColor(getContext().getResources().getColor(R.color.not_confirmed, getContext().getTheme())); + } else if (mCard.getLastInputDescription().contains("None")) { + tvInputs.setTextColor(getContext().getResources().getColor(R.color.primary_dark, getContext().getTheme())); + } else { + tvInputs.setTextColor(getContext().getResources().getColor(R.color.confirmed, getContext().getTheme())); + } + + if (tvOutputs != null) { + tvOutputs.setText(mCard.getOutputsDescription()); + } + + if (tvLastInput != null) { + tvLastInput.setText(mCard.getLastInputDescription()); + } + if (tvLastOutput != null) { + tvLastOutput.setText(mCard.getLastOutputDescription()); + } + + tvBlockchain.setText(mCard.getBlockchainName()); + ivBlockchain.setImageResource(mCard.getBlockchain().getImageResource(this.getContext(), mCard.getTokenSymbol())); + + if (tvValidationNode != null) { + tvValidationNode.setText(mCard.getValidationNodeDescription()); + } + + if (mCard.useDefaultPIN1()) { + ivPIN.setImageResource(R.drawable.unlock_pin1); + ivPIN.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Toast.makeText(getContext(), "This banknote is protected by default PIN1 code", Toast.LENGTH_LONG).show(); + } + }); + } else { + ivPIN.setImageResource(R.drawable.lock_pin1); + ivPIN.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Toast.makeText(getContext(), "This banknote is protected by user's PIN1 code", Toast.LENGTH_LONG).show(); + } + }); + } + + if (mCard.getPauseBeforePIN2() > 0 && (mCard.useDefaultPIN2() || !mCard.useSmartSecurityDelay())) { + ivPIN2orSecurityDelay.setImageResource(R.drawable.timer); + ivPIN2orSecurityDelay.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Toast.makeText(getContext(), String.format("This banknote will enforce %.0f seconds security delay for all operations requiring PIN2 code", mCard.getPauseBeforePIN2() / 1000.0), Toast.LENGTH_LONG).show(); + } + }); + + } else if (mCard.useDefaultPIN2()) { + ivPIN2orSecurityDelay.setImageResource(R.drawable.unlock_pin2); + ivPIN2orSecurityDelay.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Toast.makeText(getContext(), "This banknote is protected by default PIN2 code", Toast.LENGTH_LONG).show(); + } + }); + } else { + ivPIN2orSecurityDelay.setImageResource(R.drawable.lock_pin2); + ivPIN2orSecurityDelay.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Toast.makeText(getContext(), "This banknote is protected by user's PIN2 code", Toast.LENGTH_LONG).show(); + } + }); + } + + + if (mCard.useDevelopersFirmware()) { + ivDeveloperVersion.setImageResource(R.drawable.ic_developer_version); + ivDeveloperVersion.setVisibility(View.VISIBLE); + ivDeveloperVersion.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Toast.makeText(getContext(), "Unlocked banknote, only for development use", Toast.LENGTH_LONG).show(); + } + }); + } else { + ivDeveloperVersion.setVisibility(View.INVISIBLE); + } + + if (tvSend != null) { + if (mCard.hasBalanceInfo()) { + tvSend.setEnabled(true); + } else { + tvSend.setEnabled(false); + } + } + + if (tvPurge != null) { + if (mCard.hasBalanceInfo()) { + tvPurge.setEnabled(true); + } else { + tvPurge.setEnabled(false); + } + } + + tvIssuer.setText(mCard.getIssuerDescription()); + + timerHideErrorAndMessage = new Timer(); + + + timerHideErrorAndMessage.schedule(new TimerTask() { + @Override + public void run() { + tvError.post(new Runnable() { + @Override + public void run() { + tvMessage.setVisibility(View.GONE); + tvError.setVisibility(View.GONE); + mCard.setError(null); + mCard.setMessage(null); + } + }); + } + }, 5000); + + if (mCard.isReusable()) { + tvHeader.setText("REUSABLE WALLET"); + tvCaution.setVisibility(View.GONE); + } else { + if (mCard.getMaxSignatures() == mCard.getRemainingSignatures()) { + tvHeader.setText("BANKNOTE"); + tvCaution.setVisibility(View.GONE); + } else { + tvHeader.setText("NON-TRANSFERABLE BANKNOTE"); + tvCaution.setVisibility(View.VISIBLE); + } + } + + if (mCard.useDevelopersFirmware()) { + tvHeader.setText("DEVELOPER KIT"); + tvCaution.setVisibility(View.VISIBLE); + } + + } catch (Exception e) { + e.printStackTrace(); + } + } + + Timer timerHideErrorAndMessage = null; + + public Intent prepareResultIntent() { + Intent data = new Intent(); + data.putExtra("UID", mCard.getUID()); + data.putExtra("Card", mCard.getAsBundle()); + return data; + } + + private void SendTransaction(String tx) { + CoinEngine engine = CoinEngineFactory.Create(mCard.getBlockchain()); + if (mCard.getBlockchain() == Blockchain.Ethereum || mCard.getBlockchain() == Blockchain.EthereumTestNet || mCard.getBlockchain() == Blockchain.Token) { + ETHRequestTask task = new ETHRequestTask(mCard.getBlockchain()); + Infura_Request req = Infura_Request.SendTransaction(mCard.getWallet(), tx); + req.setID(67); + req.setBlockchain(mCard.getBlockchain()); + task.execute(req); + } else if (mCard.getBlockchain() == Blockchain.Bitcoin ||mCard.getBlockchain() == Blockchain.BitcoinTestNet) { + String nodeAddress = engine.GetNode(mCard); + int nodePort = engine.GetNodePort(mCard); + + UpdateWalletInfoTask connectTask = new UpdateWalletInfoTask(nodeAddress, nodePort); + connectTask.execute(Electrum_Request.Broadcast(mCard.getWallet(), tx)); + } + else if (mCard.getBlockchain() == Blockchain.BitcoinCash ||mCard.getBlockchain() == Blockchain.BitcoinCashTestNet) { + String nodeAddress = engine.GetNode(mCard); + int nodePort = engine.GetNodePort(mCard); + + UpdateWalletInfoTask connectTask = new UpdateWalletInfoTask(nodeAddress, nodePort); + connectTask.execute(Electrum_Request.Broadcast(mCard.getWallet(), tx)); + } + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + switch (requestCode) { + case REQUEST_CODE_ENTER_NEW_PIN: + if (resultCode == Activity.RESULT_OK) { + if (data != null) { + if (data.getExtras().containsKey("confirmPIN")) { + Intent intent = new Intent(getContext(), RequestPINActivity.class); + intent.putExtra("mode", RequestPINActivity.Mode.RequestPIN2.toString()); + intent.putExtra("UID", mCard.getUID()); + intent.putExtra("Card", mCard.getAsBundle()); + newPIN = data.getStringExtra("newPIN"); + startActivityForResult(intent, REQUEST_CODE_REQUEST_PIN2_FOR_SWAP_PIN); + } else { + Intent intent = new Intent(getContext(), RequestPINActivity.class); + intent.putExtra("newPIN", data.getStringExtra("newPIN")); + intent.putExtra("mode", RequestPINActivity.Mode.ConfirmNewPIN.toString()); + startActivityForResult(intent, REQUEST_CODE_ENTER_NEW_PIN); + } + } + } + break; + case REQUEST_CODE_ENTER_NEW_PIN2: + if (resultCode == Activity.RESULT_OK) { + if (data != null) { + if (data.getExtras().containsKey("confirmPIN2")) { + Intent intent = new Intent(getContext(), RequestPINActivity.class); + intent.putExtra("mode", RequestPINActivity.Mode.RequestPIN2.toString()); + intent.putExtra("UID", mCard.getUID()); + intent.putExtra("Card", mCard.getAsBundle()); + newPIN2 = data.getStringExtra("newPIN2"); + startActivityForResult(intent, REQUEST_CODE_REQUEST_PIN2_FOR_SWAP_PIN); + } else { + Intent intent = new Intent(getContext(), RequestPINActivity.class); + intent.putExtra("newPIN2", data.getStringExtra("newPIN2")); + intent.putExtra("mode", RequestPINActivity.Mode.ConfirmNewPIN2.toString()); + startActivityForResult(intent, REQUEST_CODE_ENTER_NEW_PIN2); + } + } + } + break; + case REQUEST_CODE_REQUEST_PIN2_FOR_SWAP_PIN: + if (resultCode == Activity.RESULT_OK) { + if (newPIN.equals("")) { + newPIN = mCard.getPIN(); + } + if (newPIN2.equals("")) { + newPIN2 = PINStorage.getPIN2(); + } + + PINSwapWarningDialog dialog = (new PINSwapWarningDialog()); + dialog.activityFragment = this; + if (!PINStorage.isDefaultPIN(newPIN) || !PINStorage.isDefaultPIN2(newPIN2)) { + dialog.message = "If you forget your new PIN you will lose your money forever!"; + } else { + dialog.message = "If you use default PIN someone can steal your money!"; + } + dialog.show(getActivity().getFragmentManager(), "PINSwapWarningDialog"); + } + break; + + case REQUEST_CODE_SWAP_PIN: + if (resultCode == Activity.RESULT_OK) { + if (data == null) { + data = new Intent(); + + data.putExtra("UID", mCard.getUID()); + data.putExtra("Card", mCard.getAsBundle()); + data.putExtra("modification", "delete"); + } else { + data.putExtra("modification", "update"); + } + getActivity().setResult(Activity.RESULT_OK, data); + getActivity().finish(); + } else { + if (data != null && data.getExtras().containsKey("UID") && data.getExtras().containsKey("Card")) { + Tangem_Card updatedCard = new Tangem_Card(data.getStringExtra("UID")); + updatedCard.LoadFromBundle(data.getBundleExtra("Card")); + mCard = updatedCard; + } + if (resultCode == CreateNewWalletActivity.RESULT_INVALID_PIN && requestPIN2Count < 2) { + requestPIN2Count++; + Intent intent = new Intent(getContext(), RequestPINActivity.class); + intent.putExtra("mode", RequestPINActivity.Mode.RequestPIN2.toString()); + intent.putExtra("UID", mCard.getUID()); + intent.putExtra("Card", mCard.getAsBundle()); + startActivityForResult(intent, REQUEST_CODE_REQUEST_PIN2_FOR_SWAP_PIN); + return; + } else { + if (data != null && data.getExtras().containsKey("message")) { + mCard.setError(data.getStringExtra("message")); + } + } + } + break; + case REQUEST_CODE_REQUEST_PIN2_FOR_PURGE: + if (resultCode == Activity.RESULT_OK) { + Intent intent = new Intent(getContext(), PurgeActivity.class); + intent.putExtra("UID", mCard.getUID()); + intent.putExtra("Card", mCard.getAsBundle()); + startActivityForResult(intent, REQUEST_CODE_PURGE); + } + break; + case REQUEST_CODE_PURGE: + if (resultCode == Activity.RESULT_OK) { + if (data == null) { + data = new Intent(); + + data.putExtra("UID", mCard.getUID()); + data.putExtra("Card", mCard.getAsBundle()); + data.putExtra("modification", "delete"); + } else { + data.putExtra("modification", "update"); + } + getActivity().setResult(Activity.RESULT_OK, data); + getActivity().finish(); + } else { + if (data != null && data.getExtras().containsKey("UID") && data.getExtras().containsKey("Card")) { + Tangem_Card updatedCard = new Tangem_Card(data.getStringExtra("UID")); + updatedCard.LoadFromBundle(data.getBundleExtra("Card")); + mCard = updatedCard; + } + if (resultCode == CreateNewWalletActivity.RESULT_INVALID_PIN && requestPIN2Count < 2) { + requestPIN2Count++; + Intent intent = new Intent(getContext(), RequestPINActivity.class); + intent.putExtra("mode", RequestPINActivity.Mode.RequestPIN2.toString()); + intent.putExtra("UID", mCard.getUID()); + intent.putExtra("Card", mCard.getAsBundle()); + startActivityForResult(intent, REQUEST_CODE_REQUEST_PIN2_FOR_PURGE); + return; + } else { + if (data != null && data.getExtras().containsKey("message")) { + mCard.setError(data.getStringExtra("message")); + } + } + UpdateViews(); + } + break; + case REQUEST_CODE_SEND_PAYMENT: + if (resultCode == Activity.RESULT_OK) { + mSwipeRefreshLayout.postDelayed(new Runnable() { + @Override + public void run() { + onRefresh(); + } + }, 10000); + mSwipeRefreshLayout.setRefreshing(true); + mCard.clearInfo(); + UpdateViews(); + } + + if (data != null) { + if (data.getExtras().containsKey("UID") && data.getExtras().containsKey("Card")) { + Tangem_Card updatedCard = new Tangem_Card(data.getStringExtra("UID")); + updatedCard.LoadFromBundle(data.getBundleExtra("Card")); + mCard = updatedCard; + } + if (data.getExtras().containsKey("message")) { + if (resultCode == Activity.RESULT_OK) { + mCard.setMessage(data.getStringExtra("message")); + } else { + mCard.setError(data.getStringExtra("message")); + } + } + UpdateViews(); + } + + break; + } + + } + + private void startSwapPINActivity() { + Intent intent = new Intent(getContext(), SwapPINActivity.class); + intent.putExtra("UID", mCard.getUID()); + intent.putExtra("Card", mCard.getAsBundle()); + intent.putExtra("newPIN", newPIN); + intent.putExtra("newPIN2", newPIN2); + startActivityForResult(intent, REQUEST_CODE_SWAP_PIN); + } + + public static Bitmap generateQrCode(String myCodeText) throws WriterException { + Hashtable hintMap = new Hashtable(); + hintMap.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H); // H = 30% damage + + QRCodeWriter qrCodeWriter = new QRCodeWriter(); + + int size = 256; + + BitMatrix bitMatrix = qrCodeWriter.encode(myCodeText, BarcodeFormat.QR_CODE, size, size, hintMap); + int width = bitMatrix.getWidth(); + Bitmap bmp = Bitmap.createBitmap(width, width, Bitmap.Config.RGB_565); + for (int x = 0; x < width; x++) { + for (int y = 0; y < width; y++) { + bmp.setPixel(y, x, bitMatrix.get(x, y) ? Color.BLACK : Color.WHITE); + } + } + return bmp; + } + + + @Override + public void onResume() { + super.onResume(); + mNfcManager.onResume(); + } + + @Override + public void onPause() { + super.onPause(); + mNfcManager.onPause(); + } + + @Override + public void onStop() { + super.onStop(); + for (UpdateWalletInfoTask ut : updateTasks) { + ut.cancel(true); + } + mNfcManager.onStop(); + } + + @Override + public void onTagDiscovered(Tag tag) { + try { + final IsoDep isoDep = IsoDep.get(tag); + if (isoDep == null) { + throw new CardProtocol.TangemException(getString(R.string.wrong_tag_err)); + } + byte UID[] = tag.getId(); + String sUID = Util.byteArrayToHexString(UID); + if (!mCard.getUID().equals(sUID)) { + Log.d(logTag, "Invalid UID: " + sUID); + mNfcManager.IgnoreTag(isoDep.getTag()); + return; + } else { + Log.v(logTag, "UID: " + sUID); + } + + if (lastReadSuccess) { + isoDep.setTimeout(1000); + } else { + isoDep.setTimeout(65000); + } + //lastTag = tag; + verifyCardTask = new VerifyCardTask(getContext(), mCard, mNfcManager, isoDep, this); + verifyCardTask.start(); + + } catch (Exception e) { + e.printStackTrace(); + } + } + + public void ErrorOnUpdate(String message) { + mCard.setError("Cannot obtain data from blockchain"); + UpdateViews(); + } + + private class ETHRequestTask extends Infura_Task { + ETHRequestTask(Blockchain blockchain) { + super(blockchain); + } + + @Override + protected void onPostExecute(List requests) { + super.onPostExecute(requests); + for (Infura_Request request : requests) { + try { + if (request.error == null) { + + if (request.isMethod(Infura_Request.METHOD_ETH_GetBalance)) { + try { + String balanceCap = request.getResultString(); + balanceCap = balanceCap.substring(2); + BigInteger l = new BigInteger(balanceCap, 16); + BigInteger d = l.divide(new BigInteger("1000000000000000000", 10)); + Long balance = d.longValue(); + + mCard.setBalanceConfirmed(balance); + mCard.setBalanceUnconfirmed(0L); + if (mCard.getBlockchain() != Blockchain.Token) + mCard.setDecimalBalance(l.toString(10)); + mCard.setDecimalBalanceAlter(l.toString(10)); + + } catch (JSONException e) { + e.printStackTrace(); + ErrorOnUpdate(e.toString()); + } + } else if (request.isMethod(Infura_Request.METHOD_ETH_Call)) { + try { + String balanceCap = request.getResultString(); + balanceCap = balanceCap.substring(2); + BigInteger l = new BigInteger(balanceCap, 16); + Long balance = l.longValue(); + + if (l.compareTo(BigInteger.ZERO) == 0) { + mCard.setBlockchainID(Blockchain.Ethereum.getID()); + mCard.addTokenToBlockchainName(); + mSwipeRefreshLayout.setRefreshing(false); + onRefresh(); + return; + } + + mCard.setBalanceConfirmed(balance); + mCard.setBalanceUnconfirmed(0L); + mCard.setDecimalBalance(l.toString(10)); + + } catch (JSONException e) { + e.printStackTrace(); + ErrorOnUpdate(e.toString()); + } + } else if (request.isMethod(Infura_Request.METHOD_ETH_GetOutTransactionCount)) { + try { + String nonce = request.getResultString(); + nonce = nonce.substring(2); + BigInteger count = new BigInteger(nonce, 16); + + mCard.SetConfirmTXCount(count); + } catch (JSONException e) { + e.printStackTrace(); + } + } else if (request.isMethod(Infura_Request.METHOD_ETH_SendRawTransaction)) { + try { + String hashTX = ""; + + try { + String tmp = request.getResultString(); + hashTX = tmp; + } catch (JSONException e) { + JSONObject msg = request.getAnswer(); + JSONObject err = msg.getJSONObject("error"); + hashTX = err.getString("message"); + LastSignStorage.setLastMessage(mCard.getWallet(), hashTX); + ErrorOnUpdate("Failed to send transaction. Try again"); + return; + } + + if (hashTX.startsWith("0x") || hashTX.startsWith("0X")) { + hashTX = hashTX.substring(2); + } + BigInteger bigInt = new BigInteger(hashTX, 16); //TODO: очень плохой способ + LastSignStorage.setTxWasSend(mCard.getWallet()); + LastSignStorage.setLastMessage(mCard.getWallet(), ""); + Log.e("TX_RESULT", hashTX); + + + BigInteger nonce = mCard.GetConfirmTXCount(); + nonce.add(BigInteger.valueOf(1)); + mCard.SetConfirmTXCount(nonce); + Log.e("TX_RESULT", hashTX); + + } catch (Exception e) { + e.printStackTrace(); + ErrorOnUpdate("Failed to send transaction. Try again"); + } + } + UpdateViews(); + } else { + ErrorOnUpdate(request.error); + } + } catch (JSONException e) { + e.printStackTrace(); + ErrorOnUpdate(e.toString()); + } + } + + if (updateTasks.size() == 0) mSwipeRefreshLayout.setRefreshing(false); + } + } + + public void OnReadStart(CardProtocol cardProtocol) { + progressBar.post(new Runnable() { + @Override + public void run() { + progressBar.setVisibility(View.VISIBLE); + progressBar.setProgress(5); + } + }); + } + + public void OnReadFinish(final CardProtocol cardProtocol) { + + verifyCardTask = null; + + if (cardProtocol != null) { + if (cardProtocol.getError() == null) { + progressBar.post(new Runnable() { + @Override + public void run() { + progressBar.setProgress(100); + progressBar.setProgressTintList(ColorStateList.valueOf(Color.GREEN)); + Intent intent = new Intent(getContext(), VerifyCardActivity.class); + // TODO обновить карту mCard + intent.putExtra("UID", cardProtocol.getCard().getUID()); + intent.putExtra("Card", cardProtocol.getCard().getAsBundle()); + startActivityForResult(intent, REQUEST_CODE_VERIFY_CARD); + //addCard(cardProtocol.getCard()); + } + }); + } else { + // remove last UIDs because of error and no card read + progressBar.post(new Runnable() { + @Override + public void run() { + lastReadSuccess = false; + if (cardProtocol.getError() instanceof CardProtocol.TangemException_ExtendedLengthNotSupported) { + if (!NoExtendedLengthSupportDialog.allreadyShowed) { + new NoExtendedLengthSupportDialog().show(getActivity().getFragmentManager(), "NoExtendedLengthSupportDialog"); + } + } else { + Toast.makeText(getContext(), "Try to scan again", Toast.LENGTH_LONG).show(); + } + progressBar.setProgress(100); + progressBar.setProgressTintList(ColorStateList.valueOf(Color.RED)); + } + }); + } + } + + progressBar.postDelayed(new Runnable() { + @Override + public void run() { + try { + progressBar.setProgress(0); + progressBar.setProgressTintList(ColorStateList.valueOf(Color.DKGRAY)); + progressBar.setVisibility(View.INVISIBLE); + } catch (Exception e) { + e.printStackTrace(); + } + } + }, 500); + } + + public void OnReadProgress(CardProtocol protocol, final int progress) { + progressBar.post(new Runnable() { + @Override + public void run() { + progressBar.setProgress(progress); + } + }); + } + + public void OnReadCancel() { + + verifyCardTask = null; + + progressBar.postDelayed(new Runnable() { + @Override + public void run() { + try { + progressBar.setProgress(0); + progressBar.setProgressTintList(ColorStateList.valueOf(Color.DKGRAY)); + progressBar.setVisibility(View.INVISIBLE); + } catch (Exception e) { + e.printStackTrace(); + } + } + }, 500); + } + + public void OnReadWait(int msec) { + WaitSecurityDelayDialog.OnReadWait(getActivity(), msec); + } + + @Override + public void OnReadBeforeRequest(int timeout) { + WaitSecurityDelayDialog.onReadBeforeRequest(getActivity(), timeout); + } + + @Override + public void OnReadAfterRequest() { + WaitSecurityDelayDialog.onReadAfterRequest(getActivity()); + } + + + private class RateInfoTask extends ExchangeTask { + protected void onPostExecute(List requests) { + super.onPostExecute(requests); + for (ExchangeRequest request : requests) { + if (request.error == null) { + try { + + JSONArray arr = request.getAnswerList(); + for (int i = 0; i < arr.length(); ++i) { + JSONObject obj = arr.getJSONObject(i); + String currency = obj.getString("id"); + + boolean stop = false; + boolean stopAlter = false; + if (currency.equals(request.currency)) { + String usd = obj.getString("price_usd"); + + Float rate = Float.valueOf(usd); + mCard.setRate(rate); + UpdateViews(); + stop = true; + } + + if (currency.equals(request.currencyAlter)) { + String usd = obj.getString("price_usd"); + + Float rate = Float.valueOf(usd); + mCard.setRateAlter(rate); + UpdateViews(); + stopAlter = true; + } + + if (stop && stopAlter) { + break; + } + + } + } catch (JSONException e) { + e.printStackTrace(); + } + } + } + } + } + + private class UpdateWalletInfoTask extends Electrum_Task { + public UpdateWalletInfoTask(String host, int port) { + super(host, port); + } + + + public UpdateWalletInfoTask(String host, int port, SharedData sharedData) { + super(host, port, sharedData); + } + + @Override + protected void onProgressUpdate(Integer... values) { + super.onProgressUpdate(values); + } + + @Override + protected void onCancelled() { + super.onCancelled(); + updateTasks.remove(this); + if (updateTasks.size() == 0) mSwipeRefreshLayout.setRefreshing(false); + } + + @Override + protected void onPostExecute(List requests) { + super.onPostExecute(requests); + Log.i("RequestWalletInfoTask", "onPostExecute[" + String.valueOf(updateTasks.size()) + "]"); + updateTasks.remove(this); + + CoinEngine engine = CoinEngineFactory.Create(mCard.getBlockchain()); + + for (Electrum_Request request : requests) { + try { + if (request.error == null) { + if (request.isMethod(Electrum_Request.METHOD_GetBalance)) { + try { + String mWalletAddress = request.getParams().getString(0); + Long confBalance = request.getResult().getLong("confirmed"); + Long unconf = request.getResult().getLong("unconfirmed"); + if (sharedCounter != null) { + int counter = sharedCounter.requestCounter.incrementAndGet(); + if (counter != 1) { + continue; + } + } + + mCard.setBalanceConfirmed(confBalance); + mCard.setBalanceUnconfirmed(unconf); + mCard.setDecimalBalance(String.valueOf(confBalance)); + mCard.setValidationNodeDescription(getValidationNodeDescription()); + } catch (JSONException e) { + if (sharedCounter != null) { + int errCounter = sharedCounter.errorRequest.incrementAndGet(); + if (errCounter >= sharedCounter.allRequest) { + e.printStackTrace(); + ErrorOnUpdate(e.toString()); + engine.SwitchNode(mCard); + } + } else { + e.printStackTrace(); + ErrorOnUpdate(e.toString()); + engine.SwitchNode(mCard); + } + } + } else if (request.isMethod(Electrum_Request.METHOD_SendTransaction)) { + try { + String hashTX = request.getResultString(); + + try { + LastSignStorage.setLastMessage(mCard.getWallet(), hashTX); + if (hashTX.startsWith("0x") || hashTX.startsWith("0X")) { + hashTX = hashTX.substring(2); + } + BigInteger bigInt = new BigInteger(hashTX, 16); //TODO: очень плохой способ + LastSignStorage.setTxWasSend(mCard.getWallet()); + LastSignStorage.setLastMessage(mCard.getWallet(), ""); + Log.e("TX_RESULT", hashTX); + + } catch (Exception e) { + engine.SwitchNode(mCard); + ErrorOnUpdate("Failed to send transaction. Try again."); + } + + } catch (JSONException e) { + e.printStackTrace(); + ErrorOnUpdate("Failed to send transaction. Try again."); + engine.SwitchNode(mCard); + } + } else if (request.isMethod(Electrum_Request.METHOD_ListUnspent)) { + try { + String mWalletAddress = request.getParams().getString(0); + + JSONArray jsUnspentArray = request.getResultArray(); + try { + mCard.getUnspentTransactions().clear(); + for (int i = 0; i < jsUnspentArray.length(); i++) { + JSONObject jsUnspent = jsUnspentArray.getJSONObject(i); + Tangem_Card.UnspentTransaction trUnspent = new Tangem_Card.UnspentTransaction(); + trUnspent.txID = jsUnspent.getString("tx_hash"); + trUnspent.Amount = jsUnspent.getInt("value"); + trUnspent.Height = jsUnspent.getInt("height"); + mCard.getUnspentTransactions().add(trUnspent); + } + } catch (JSONException e) { + e.printStackTrace(); + ErrorOnUpdate(e.toString()); + engine.SwitchNode(mCard); + } + + for (int i = 0; i < jsUnspentArray.length(); i++) { + JSONObject jsUnspent = jsUnspentArray.getJSONObject(i); + Integer height = jsUnspent.getInt("height"); + String hash = jsUnspent.getString("tx_hash"); + if (height != -1) { + String nodeAddress = engine.GetNextNode(mCard); + int nodePort = engine.GetNextNodePort(mCard); + UpdateWalletInfoTask updateWalletInfoTask = new UpdateWalletInfoTask(nodeAddress, nodePort); + + updateTasks.add(updateWalletInfoTask); + + updateWalletInfoTask.execute(Electrum_Request.GetHeader(mWalletAddress, String.valueOf(height)), + Electrum_Request.GetTransaction(mWalletAddress, hash)); + } + } + } catch (JSONException e) { + e.printStackTrace(); + ErrorOnUpdate(e.toString()); + engine.SwitchNode(mCard); + } + } else if (request.isMethod(Electrum_Request.METHOD_GetHistory)) { + try { + String mWalletAddress = request.getParams().getString(0); + + JSONArray jsHistoryArray = request.getResultArray(); + try { + mCard.getHistoryTransactions().clear(); + for (int i = 0; i < jsHistoryArray.length(); i++) { + JSONObject jsUnspent = jsHistoryArray.getJSONObject(i); + Tangem_Card.HistoryTransaction trHistory = new Tangem_Card.HistoryTransaction(); + trHistory.txID = jsUnspent.getString("tx_hash"); + trHistory.Height = jsUnspent.getInt("height"); + mCard.getHistoryTransactions().add(trHistory); + } + } catch (JSONException e) { + e.printStackTrace(); + ErrorOnUpdate(e.toString()); + engine.SwitchNode(mCard); + } + + for (int i = 0; i < jsHistoryArray.length(); i++) { + JSONObject jsUnspent = jsHistoryArray.getJSONObject(i); + Integer height = jsUnspent.getInt("height"); + String hash = jsUnspent.getString("tx_hash"); + if (height != -1) { + + String nodeAddress = engine.GetNode(mCard); + int nodePort = engine.GetNodePort(mCard); + UpdateWalletInfoTask updateWalletInfoTask = new UpdateWalletInfoTask(nodeAddress, nodePort); + updateTasks.add(updateWalletInfoTask); + + updateWalletInfoTask.execute(Electrum_Request.GetHeader(mWalletAddress, String.valueOf(height)), + Electrum_Request.GetTransaction(mWalletAddress, hash)); + } + + } + } catch (JSONException e) { + e.printStackTrace(); + ErrorOnUpdate(e.toString()); + engine.SwitchNode(mCard); + } + } else if (request.isMethod(Electrum_Request.METHOD_GetHeader)) { + try { + JSONObject jsHeader = request.getResult(); + try { + mCard.getHaedersInfo(); + mCard.UpdateHeaderInfo(new Tangem_Card.HeaderInfo( + jsHeader.getInt("block_height"), + jsHeader.getInt("timestamp"))); + } catch (JSONException e) { + e.printStackTrace(); + ErrorOnUpdate(e.toString()); + engine.SwitchNode(mCard); + + } + + } catch (JSONException e) { + e.printStackTrace(); + ErrorOnUpdate(e.toString()); + engine.SwitchNode(mCard); + + } + } else if (request.isMethod(Electrum_Request.METHOD_GetTransaction)) { + try { + + String txHash = request.TxHash; + String raw = request.getResultString(); + + List listTx = mCard.getUnspentTransactions(); + for (Tangem_Card.UnspentTransaction tx : listTx) { + if (tx.txID.equals(txHash)) { + tx.Raw = raw; + } + } + + List listHTx = mCard.getHistoryTransactions(); + for (Tangem_Card.HistoryTransaction tx : listHTx) { + if (tx.txID.equals(txHash)) { + tx.Raw = raw; + try { + ArrayList prevHashes = BTCUtils.getPrevTX(raw); + + boolean isOur = false; + for (byte[] hash : prevHashes) { + String checkID = BTCUtils.toHex(hash); + for (Tangem_Card.HistoryTransaction txForCheck : listHTx) { + if (txForCheck.txID == checkID) { + isOur = true; + } + } + } + + tx.isInput = !isOur; + } catch (BitcoinException e) { + e.printStackTrace(); + ErrorOnUpdate(e.toString()); + } + Log.e("TX", raw); + } + } + + } catch (JSONException e) { + e.printStackTrace(); + ErrorOnUpdate(e.toString()); + engine.SwitchNode(mCard); + } + } + UpdateViews(); + } else { + if (sharedCounter != null) { + int errCounter = sharedCounter.errorRequest.incrementAndGet(); + if (errCounter >= sharedCounter.allRequest) { + ErrorOnUpdate(request.error); + engine.SwitchNode(mCard); + + } + } else { + ErrorOnUpdate(request.error); + engine.SwitchNode(mCard); + + } + + } + } catch (JSONException e) { + if (sharedCounter != null) { + int errCounter = sharedCounter.errorRequest.incrementAndGet(); + if (errCounter >= sharedCounter.allRequest) { + e.printStackTrace(); + ErrorOnUpdate(e.toString()); + } + } else { + e.printStackTrace(); + ErrorOnUpdate(e.toString()); + } + } + } + if (updateTasks.size() == 0) mSwipeRefreshLayout.setRefreshing(false); + + } + } + + public static class PINSwapWarningDialog extends DialogFragment { + + LoadedWalletActivityFragment activityFragment = null; + String message; + + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + + return new AlertDialog.Builder(getActivity()) + .setIcon(R.drawable.tangem_logo_small_new) + .setTitle("Your money is at risk!") + .setMessage(message) + .setCancelable(true) + .setNegativeButton("Cancel", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + PINSwapWarningDialog.this.dismiss(); + } + }) + .setPositiveButton("Continue", + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int whichButton) { + if (activityFragment != null) + activityFragment.startSwapPINActivity(); + } + } + ) + .create(); + } + + @Override + public void onCancel(DialogInterface dialog) { + super.onCancel(dialog); + } + } + +} diff --git a/app/src/main/java/com/tangem/wallet/LogFileProvider.java b/app/src/main/java/com/tangem/wallet/LogFileProvider.java new file mode 100644 index 0000000000..a4f710d5fc --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/LogFileProvider.java @@ -0,0 +1,108 @@ +package com.tangem.wallet; + +import android.content.ContentProvider; +import android.content.ContentValues; +import android.content.UriMatcher; +import android.database.Cursor; +import android.net.Uri; +import android.os.ParcelFileDescriptor; +import android.util.Log; + +import java.io.File; +import java.io.FileNotFoundException; + +/** + * Created by dvol on 15.02.2018. + */ + +public class LogFileProvider extends ContentProvider { + + private static final String CLASS_NAME = "LogFileProvider"; + + // The authority is the symbolic name for the provider class + public static final String AUTHORITY = "com.tangem.wallet.LogFileProvider"; + + // UriMatcher used to match against incoming requests + private UriMatcher uriMatcher; + + @Override + public boolean onCreate() { + uriMatcher = new UriMatcher(UriMatcher.NO_MATCH); + + // Add a URI to the matcher which will match against the form + // 'content://it.my.app.LogFileProvider/*' + // and return 1 in the case that the incoming Uri matches this pattern + uriMatcher.addURI(AUTHORITY, "*", 1); + + return true; + } + + @Override + public ParcelFileDescriptor openFile(Uri uri, String mode) + throws FileNotFoundException { + + String LOG_TAG = CLASS_NAME+"-oF"; + + Log.v(LOG_TAG, + "Called with uri: '" + uri + "'." + uri.getLastPathSegment()); + + // Check incoming Uri against the matcher + switch (uriMatcher.match(uri)) { + + // If it returns 1 - then it matches the Uri defined in onCreate + case 1: + + // The desired file name is specified by the last segment of the + // path + // E.g. + // 'content://it.my.app.LogFileProvider/Test.txt' + // Take this and build the path to the file + String fileLocation = getContext().getCacheDir() + File.separator + + uri.getLastPathSegment(); + + // Create & return a ParcelFileDescriptor pointing to the file + // Note: I don't care what mode they ask for - they're only getting + // read only + ParcelFileDescriptor pfd = ParcelFileDescriptor.open(new File( + fileLocation), ParcelFileDescriptor.MODE_READ_ONLY); + return pfd; + + // Otherwise unrecognised Uri + default: + Log.v(LOG_TAG, "Unsupported uri: '" + uri + "'."); + throw new FileNotFoundException("Unsupported uri: " + + uri.toString()); + } + } + + // ////////////////////////////////////////////////////////////// + // Not supported / used / required for this example + // ////////////////////////////////////////////////////////////// + + @Override + public int update(Uri uri, ContentValues contentvalues, String s, + String[] as) { + return 0; + } + + @Override + public int delete(Uri uri, String s, String[] as) { + return 0; + } + + @Override + public Uri insert(Uri uri, ContentValues contentvalues) { + return null; + } + + @Override + public String getType(Uri uri) { + return null; + } + + @Override + public Cursor query(Uri uri, String[] projection, String s, String[] as1, + String s1) { + return null; + } +} diff --git a/app/src/main/java/com/tangem/wallet/Logger.java b/app/src/main/java/com/tangem/wallet/Logger.java new file mode 100644 index 0000000000..7b2d67f28c --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/Logger.java @@ -0,0 +1,285 @@ +package com.tangem.wallet; + +import android.content.Context; +import android.util.Log; + +import com.tangem.cardReader.Util; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.Date; + +public class Logger { + + public static File collectLogs(Context context) { + File f = new File(context.getCacheDir().getAbsolutePath() + "/Wallet_" + Util.formatDateTimeToFileName(new Date()) + ".log"); + try { + if (f.createNewFile()) { + f.setReadable(true); + FileWriter fileWriter = new FileWriter(f, true); + Process process = Runtime.getRuntime().exec("logcat -d -b main -v time"); + try { + InputStream is = process.getInputStream(); + InputStreamReader isr = new InputStreamReader(is); + BufferedReader bufferedReader = new BufferedReader(isr); + BufferedWriter buf = new BufferedWriter(fileWriter); + buf.append("Tangem Wallet logs"); + buf.newLine(); + + int i = 0; + String line; + while ((line = bufferedReader.readLine()) != null) { + buf.append(line); + buf.newLine(); + i++; + } + Log.e("Logger", String.format("%d log lines collected", i)); + buf.newLine(); + buf.flush(); + buf.close(); + + } finally { + process.destroy(); + } + + return f; + } + } catch (Exception e) { + e.printStackTrace(); + } + + return null; + } + + +} + +//public class Logger { +// +// public static File[] getLastLogFiles() { +// File path = new File(Environment.getExternalStorageDirectory(), "Tangem/logs"); +// if (!path.exists()) { +// return null; +// } +// File[] files = path.listFiles(); +// Arrays.sort(files, new Comparator() { +// @Override +// public int compare(File o1, File o2) { +// if (o1.lastModified() < o2.lastModified()) { +// return -1; +// } else if (o1.lastModified() > o2.lastModified()) { +// return 1; +// } +// return 0; +// } +// }); +// if (files.length < 5) return files; +// return Arrays.copyOfRange(files, files.length - 5, files.length); +// } +// +// private static File logFile = null; +// +// private static void initLogFile(Context context) { +// try { +// File path = new File(Environment.getExternalStorageDirectory(), "Tangem/logs"); +// if (!path.exists()) { +// path.mkdirs(); +// MediaScannerConnection.scanFile(context, new String[]{path.getParentFile().toString()}, null, null); +// } +// logFile = new File(path, String.format("wallet_%s.log", Util.formatDateTimeToFileName(new Date()))); +// logFile.createNewFile(); +// logFile.setReadable(true); +// +// // initiate media scan and put the new things into the path array to +// // make the scanner aware of the location and the files you want to see +// MediaScannerConnection.scanFile(context, new String[]{logFile.getAbsolutePath()}, null, null); +// } catch (IOException e) { +// e.printStackTrace(); +// } +// +// } +// +// public static boolean isCurrent(File f) { +// if (f == null || logFile == null) return false; +// return f.getAbsolutePath().equals(logFile.getAbsolutePath()); +// } +// +// +// private static class LogCatThread extends Thread { +// private boolean Terminated; +// +// private static final Object oSync = new Object(); +// +// public void Terminate() { +// Terminated = true; +// synchronized (oSync) { +// oSync.notifyAll(); +// } +// try { +// join(1000); +// } catch (InterruptedException e) { +// e.printStackTrace(); +// interrupt(); +// } +// } +// +// public void collectLogs(Writer out) { +// try { +// Process process = Runtime.getRuntime().exec("logcat -d -b main -v time"); +// try { +// InputStream is = process.getInputStream(); +// InputStreamReader isr = new InputStreamReader(is); +// BufferedReader bufferedReader = new BufferedReader(isr); +// try { +// +// try { +// //BufferedWriter for performance, true to set append to file flag +// BufferedWriter buf = new BufferedWriter(out); +// +// while (!Terminated && isr.ready()) { +// String line = bufferedReader.readLine(); +// buf.append(line); +// buf.newLine(); +// } +// //Log.i("Logger",String.format("%d lines added",i)); +// buf.newLine(); +// buf.close(); +// } catch (IOException e) { +// e.printStackTrace(); +// } +// +// +// } catch (Exception e) { +// e.printStackTrace(); +// } +// +// } finally { +// process.destroy(); +// } +// } catch (IOException e) { +// e.printStackTrace(); +// } +// +// } +// +// @Override +// public void run() { +// Process process = null; +// try { +// if (logFile == null) return; +// process = Runtime.getRuntime().exec("logcat -b main -v time"); +// try { +// InputStream is = process.getInputStream(); +// InputStreamReader isr = new InputStreamReader(is); +// BufferedReader bufferedReader = new BufferedReader(isr); +// while (!Terminated) { +// try { +// synchronized (oSync) { +// oSync.wait(1000); +// } +// if (!logFile.exists()) { +// try { +// logFile.createNewFile(); +// } catch (IOException e) { +// e.printStackTrace(); +// } +// } +// try { +// //BufferedWriter for performance, true to set append to file flag +// BufferedWriter buf = new BufferedWriter(new FileWriter(logFile, true)); +// +// while (isr.ready()) { +// String line = bufferedReader.readLine(); +// buf.append(line); +// buf.newLine(); +// } +// //Log.i("Logger",String.format("%d lines added",i)); +// buf.newLine(); +// buf.close(); +// } catch (IOException e) { +// e.printStackTrace(); +// } +// +// +// } catch (Exception e) { +// e.printStackTrace(); +// } +// } +// } finally { +// process.destroy(); +// } +// } catch (IOException e) { +// e.printStackTrace(); +// } +// } +// } +// +// static LogCatThread t = new LogCatThread(); +// +// public static void StartSaveToFile(Activity activity) { +// try { +// if (logFile != null) return; +// +// verifyStoragePermissions(activity); +// initLogFile(activity.getApplicationContext()); +// t.start(); +// } catch (Exception e) { +// e.printStackTrace(); +// } +// } +// +// public static void StopSaveToFile(Context context) { +// final Object oSync = new Object(); +// if (t.isAlive()) { +// t.Terminate(); +// } +// if (logFile != null) { +// MediaScannerConnection.scanFile(context, new String[]{logFile.getAbsolutePath().toString()}, null, new MediaScannerConnection.OnScanCompletedListener() { +// @Override +// public void onScanCompleted(String path, Uri uri) { +//// synchronized (oSync) { +//// oSync.notifyAll(); +//// } +// } +// }); +//// try { +//// synchronized (oSync) { +//// oSync.wait(10000); +// logFile = null; +//// } +//// } catch (InterruptedException e) { +//// e.printStackTrace(); +//// } +// } +// +// } +// +// // Storage Permissions +// private static final int REQUEST_EXTERNAL_STORAGE = 1; +// private static String[] PERMISSIONS_STORAGE = { +// Manifest.permission.READ_EXTERNAL_STORAGE, +// Manifest.permission.WRITE_EXTERNAL_STORAGE +// }; +// +// //Checks if the app has permission to write to device storage +// //If the app does not has permission then the user will be prompted to grant permissions +// public static void verifyStoragePermissions(Activity activity) { +// // Check if we have write permission +// int permission = ActivityCompat.checkSelfPermission(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE); +// +// if (permission != PackageManager.PERMISSION_GRANTED) { +// // We don't have permission so prompt the user +// ActivityCompat.requestPermissions( +// activity, +// PERMISSIONS_STORAGE, +// REQUEST_EXTERNAL_STORAGE +// ); +// } +// } +// +//} + diff --git a/app/src/main/java/com/tangem/wallet/LogoActivity.java b/app/src/main/java/com/tangem/wallet/LogoActivity.java new file mode 100644 index 0000000000..913af1e0f0 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/LogoActivity.java @@ -0,0 +1,68 @@ +package com.tangem.wallet; + +import android.content.Intent; +import android.os.Bundle; +import android.support.v7.app.AppCompatActivity; +import android.view.View; +import android.widget.ImageView; +import android.widget.TextView; + +/** + * An example full-screen activity that shows and hides the system UI (i.e. + * status bar and navigation/system bar) with user interaction. + */ +public class LogoActivity extends AppCompatActivity { + + private final Runnable mHideRunnable = new Runnable() { + @Override + public void run() { + hide(); + } + }; + + ImageView imgLogo; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setContentView(R.layout.activity_logo); + + imgLogo= (ImageView) findViewById(R.id.imgLogo); + // Set up the user interaction to manually show or hide the system UI. + imgLogo.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) {hide(); + } + }); + } + + @Override + protected void onPostCreate(Bundle savedInstanceState) { + super.onPostCreate(savedInstanceState); + + // Trigger the initial hide() shortly after the activity has been + // created, to briefly hint to the user that UI controls + // are available. + TextView AppVersion = (TextView) findViewById(R.id.AppVersion); + AppVersion.setText("BETA v." + BuildConfig.VERSION_NAME); + if( !getIntent().getBooleanExtra("skipAutoHide",false)) { + delayedHide(1000); + } + } + + private void hide() { + Intent intent=new Intent(getBaseContext(),MainActivity.class); + startActivity(intent); + finish(); + } + + /** + * Schedules a call to hide() in [delay] milliseconds, canceling any + * previously scheduled calls. + */ + private void delayedHide(int delayMillis) { + //imgLogo.removeCallbacks(mHideRunnable); + imgLogo.postDelayed(mHideRunnable, delayMillis); + } +} diff --git a/app/src/main/java/com/tangem/wallet/MainActivity.java b/app/src/main/java/com/tangem/wallet/MainActivity.java new file mode 100644 index 0000000000..c65f71fd97 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/MainActivity.java @@ -0,0 +1,404 @@ +package com.tangem.wallet; + +import android.app.AlertDialog; +import android.app.Dialog; +import android.app.DialogFragment; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.pm.ResolveInfo; +import android.net.Uri; +import android.nfc.NfcAdapter; +import android.nfc.Tag; +import android.os.Bundle; +import android.support.design.widget.FloatingActionButton; +import android.support.v7.app.AppCompatActivity; +import android.support.v7.widget.PopupMenu; +import android.util.Log; +import android.view.KeyEvent; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.animation.Animation; +import android.view.animation.DecelerateInterpolator; +import android.view.animation.Transformation; +import android.widget.LinearLayout; +import android.widget.RelativeLayout; +import android.widget.TextView; + +import com.scottyab.rootbeer.RootBeer; +import com.skyfishjy.library.RippleBackground; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.util.List; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; + + +public class MainActivity extends AppCompatActivity implements PopupMenu.OnMenuItemClickListener { + + public static final int DIALOG_ENABLE_INTERNET = 1; + private static final int REQUEST_CODE_SEND_EMAIL = 2; + private String logTag = "MainActivity"; + + public interface OnCardsClean { + void doClean(); + } + +// public interface OnCreateNFCDialog { +// Dialog CreateNFCDialog(int id, AlertDialogWrapper.Builder builder, LayoutInflater li); +// } + + OnCardsClean onCardsClean; +// OnCreateNFCDialog onCreateNFCDialog; + NfcAdapter.ReaderCallback onNFCReaderCallback; + FloatingActionButton fab; + + + public void setOnCardsClean(OnCardsClean onCardsClean) { + this.onCardsClean = onCardsClean; + } + +// public void setOnCreateNFCDialog(OnCreateNFCDialog onCreateNFCDialog) { +// this.onCreateNFCDialog = onCreateNFCDialog; +// } + + public void setNfcAdapterReaderCallback(NfcAdapter.ReaderCallback callback) { + this.onNFCReaderCallback = callback; + } + + public void showCleanButton() { + + findViewById(R.id.tvTapPrompt).setVisibility(View.INVISIBLE); + } + + public void hideCleanButton() { + findViewById(R.id.tvTapPrompt).setVisibility(View.VISIBLE); + } + + public static class RootFoundDialog extends DialogFragment { + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + + return new AlertDialog.Builder(getActivity()) + .setIcon(R.drawable.tangem_logo_small_new) + .setTitle("Your Android device is rooted. Security at risk!") + .setCancelable(false) + .setPositiveButton("Got it", + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int whichButton) { + } + } + ) + .create(); + } + + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + RootBeer rootBeer = new RootBeer(this); + if (rootBeer.isRootedWithoutBusyBoxCheck()) { + //we found indication of root + new RootFoundDialog().show(getFragmentManager(), "RootFoundDialog"); + } + + setContentView(R.layout.activity_main); + setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_NOSENSOR); + + commonInit(getApplicationContext()); + + TextView tvNFCHint = findViewById(R.id.tvNFCHint); + if(tvNFCHint != null) + { +// tvNFCHint.setText("Scan a banknote with your\n" + PhoneUtility.GetPhoneName() + "\nas shown above"); + tvNFCHint.setText("Scan a banknote with your\n smartphone as shown above"); + } + + DeviceNFCAntennaLocation antenna = new DeviceNFCAntennaLocation(); + antenna.getAntennaLocation(); + final LinearLayout hand = findViewById(R.id.llHand); + final LinearLayout nfc = findViewById(R.id.llNFC); + final RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams) hand.getLayoutParams(); + final RelativeLayout.LayoutParams lp2 = (RelativeLayout.LayoutParams) nfc.getLayoutParams(); + final float dp = getResources().getDisplayMetrics().density; + final float lm = dp*(69 + antenna.X * 75); + lp.topMargin = (int) (dp*(-100 + antenna.Y * 250)); + lp2.topMargin = (int) (dp*(-125 + antenna.Y * 250)); + nfc.setLayoutParams(lp2); + + Animation a = new Animation() { + + @Override + protected void applyTransformation(float interpolatedTime, Transformation t) { + lp.leftMargin = (int)(lm * interpolatedTime); + hand.setLayoutParams(lp); + } + }; + a.setDuration(2000); // in ms + a.setInterpolator(new DecelerateInterpolator()); + hand.startAnimation(a); + + fab = findViewById(R.id.fab); + fab.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + + showMenu(view); + } + }); + + MainActivityFragment mainActivityFragment=(MainActivityFragment)getSupportFragmentManager().findFragmentById(R.id.fragmentMain); + if( mainActivityFragment.getCardListAdapter().getItemCount()>0 ) + { + showCleanButton(); + }else { + hideCleanButton(); + } + + final RippleBackground rippleBackground=(RippleBackground)findViewById(R.id.imNFC); + rippleBackground.startRippleAnimation(); + + + Intent intent = getIntent(); + if (intent != null && (NfcAdapter.ACTION_TECH_DISCOVERED.equals(intent.getAction()) || NfcAdapter.ACTION_NDEF_DISCOVERED.equals(intent.getAction()))) { + Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG); + if (tag != null && onNFCReaderCallback != null) { + onNFCReaderCallback.onTagDiscovered(tag); + } + } + } + + public static void commonInit(Context context) + { + if( PINStorage.needInit() ) { + PINStorage.Init(context); + } + if( LastSignStorage.needInit() ) { + LastSignStorage.Init(context); + } + } + + @Override + protected void onDestroy() { +// Logger.StopSaveToFile(getApplicationContext()); + super.onDestroy(); + } + + @Override + protected void onNewIntent(Intent intent) { + super.onNewIntent(intent); + if (intent != null && (NfcAdapter.ACTION_TECH_DISCOVERED.equals(intent.getAction()) || NfcAdapter.ACTION_NDEF_DISCOVERED.equals(intent.getAction()))) { + Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG); + if (tag != null && onNFCReaderCallback != null) { + onNFCReaderCallback.onTagDiscovered(tag); + } + } + + } + + @Override + public boolean onKeyDown(int keycode, KeyEvent e) { + switch (keycode) { + case KeyEvent.KEYCODE_MENU: + fab.requestFocus(); + showMenu(fab); + return true; + } + + return super.onKeyDown(keycode, e); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + // Inflate the menu; this adds items to the action bar if it is present. + getMenuInflater().inflate(R.menu.menu_main, menu); + if( BuildConfig.DEBUG ) { + for(int i=0; i 0) { + String[] fileNames = new String[filelocations.length]; + for (int i = 0; i < filelocations.length; i++) + fileNames[i] = filelocations[i].getAbsolutePath(); + zipFile = File.createTempFile("tangemLogs", ".zip", filelocations[0].getParentFile()); + Compress compress = new Compress(fileNames, zipFile.getAbsolutePath()); + compress.zip(); + Log.e(logTag, String.format("Send %d bytes zip with logs", zipFile.length())); + Uri attachment = Uri.parse("content://" + LogFileProvider.AUTHORITY + "/" + + zipFile.getName()); + + intent.putExtra(Intent.EXTRA_STREAM, attachment); + zipFile.deleteOnExit(); + } + + List activities = getPackageManager().queryIntentActivities(intent, 0); + boolean isIntentSafe = activities.size() > 0; + + if (isIntentSafe) { + startActivity(intent); + return; + } + } catch (Exception e) { + e.printStackTrace(); + } + + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + if (requestCode == REQUEST_CODE_SEND_EMAIL) { + if (zipFile != null) { + zipFile.delete(); + zipFile = null; + } + } + super.onActivityResult(requestCode, resultCode, data); + } + + public void showMenu(View v) { + PopupMenu popup = new PopupMenu(this, v); + MenuInflater inflater = popup.getMenuInflater(); + inflater.inflate(R.menu.menu_main, popup.getMenu()); + if( BuildConfig.DEBUG ) { + for(int i=0; i slCardUIDs = new ArrayList<>(); + private static final String logTag = "MainActivityFragment"; + private ProgressBar progressBar; + + private CardListAdapter mCardListAdapter; + + public CardListAdapter getCardListAdapter() { + return mCardListAdapter; + } + + private ReadCardInfoTask readCardInfoTask; + private SwipeRefreshLayout mSwipeRefreshLayout; //TODO: tmp + List requestTasks = new ArrayList<>(); + + public MainActivityFragment() { + } + + @Override + public void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + mCardListAdapter.onSaveInstanceState(outState); + outState.putStringArrayList("slCardUIDs", slCardUIDs); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View result = inflater.inflate(R.layout.fragment_main, container, false); + + mNfcManager = new NfcManager(this.getActivity(), this); + verifyPermissions(); + + progressBar = result.findViewById(R.id.progressBar); + progressBar.setProgressTintList(ColorStateList.valueOf(Color.DKGRAY)); + RecyclerView rvCards = result.findViewById(R.id.lvCards); + rvCards.setLayoutManager(new LinearLayoutManager(getContext())); + mCardListAdapter = new CardListAdapter(getActivity().getLayoutInflater(), savedInstanceState, this); + rvCards.setAdapter(mCardListAdapter); + if (savedInstanceState != null && savedInstanceState.containsKey("slCardUIDs")) { + slCardUIDs = savedInstanceState.getStringArrayList("slCardUIDs"); + } + + // SwipeRefreshLayout + mSwipeRefreshLayout = result.findViewById(R.id.swipe_container); //TODO: tmp + + if( mSwipeRefreshLayout!=null ) { + SwipeRefreshLayout.OnRefreshListener onRefreshListener = new SwipeRefreshLayout.OnRefreshListener() { + + @Override + public void onRefresh() { + //Update + // Showing refresh animation before making http call + + if (requestTasks.size() > 0) return; + + if (requestTasks.size() > 0) { //TODO: tmp + mSwipeRefreshLayout.setRefreshing(true); + } else { + mSwipeRefreshLayout.setRefreshing(false); + } + } + }; + mSwipeRefreshLayout.setOnRefreshListener(onRefreshListener); //TODO: tmp + } + + ItemTouchHelper itemTouchHelper = new ItemTouchHelper(new ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT) { + @Override + public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) { + return false; + } + + @Override + public void onSwiped(RecyclerView.ViewHolder viewHolder, int swipeDir) { + //Remove swiped item from list and notify the RecyclerView + int cardIndex = viewHolder.getAdapterPosition(); + if (cardIndex < 0 || cardIndex >= mCardListAdapter.getItemCount()) return; + slCardUIDs.remove(mCardListAdapter.getCard(cardIndex).getUID()); + if (mCardListAdapter.getCard(cardIndex).getUID() == lastRead_UID) { + lastRead_UID = ""; + } + mCardListAdapter.removeCard(cardIndex); + if (mCardListAdapter.getItemCount() == 0 && getActivity().getClass() == MainActivity.class) { + ((MainActivity) getActivity()).hideCleanButton(); + } + } + }); + + itemTouchHelper.attachToRecyclerView(rvCards); + + if (getActivity() instanceof MainActivity) { + ((MainActivity) getActivity()).setOnCardsClean(this); +// ((MainActivity) getActivity()).setOnCreateNFCDialog(this); + ((MainActivity) getActivity()).setNfcAdapterReaderCallback(this); + } + + return result; + } + + private void verifyPermissions() { + NfcManager.verifyPermissions(getActivity()); + if (ActivityCompat.checkSelfPermission(getActivity(), Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) { + Log.e("QRScanActivity", "User hasn't granted permission to use camera"); + ActivityCompat.requestPermissions(getActivity(), new String[]{Manifest.permission.CAMERA}, REQUEST_CODE_REQUEST_CAMERA_PERMISSIONS); + } + } + + int unsuccessReadCount = 0; + Tag lastTag = null; + + @Override + public void onTagDiscovered(Tag tag) { + try { + // get IsoDep handle and run cardReader thread + final IsoDep isoDep = IsoDep.get(tag); + if (isoDep == null) { + throw new CardProtocol.TangemException(getString(R.string.wrong_tag_err)); + } + + byte UID[] = tag.getId(); + String sUID = Util.byteArrayToHexString(UID); + if (slCardUIDs.indexOf(sUID) != -1) { + Log.d(logTag, "Repeat UID: " + sUID); + mNfcManager.IgnoreTag(isoDep.getTag()); + return; + } else { + Log.v(logTag, "UID: " + sUID); + } + +// Log.e(logTag,"setTimeout("+String.valueOf(1000 + 3000 * unsuccessReadCount)+")"); + if (unsuccessReadCount < 2) { + isoDep.setTimeout(2000 + 5000 * unsuccessReadCount); + } else { + isoDep.setTimeout(90000); + } + lastTag = tag; + + readCardInfoTask = new ReadCardInfoTask(isoDep, this); + readCardInfoTask.start(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + @Override + public void onResume() { + super.onResume(); + + mNfcManager.onResume(); + } + + @Override + public void onPause() { + mNfcManager.onPause(); + if (readCardInfoTask != null) { + readCardInfoTask.cancel(true); + } + super.onPause(); + } + + @Override + public void onStop() { + // dismiss enable NFC dialog + mNfcManager.onStop(); + if (readCardInfoTask != null) { + readCardInfoTask.cancel(true); + } + for (RequestWalletInfoTask rt : requestTasks) { + rt.cancel(true); + } + super.onStop(); + } + + public void refreshCard(Tangem_Card card) { + CoinEngine engine = CoinEngineFactory.Create(card.getBlockchain()); + if (card.getBlockchain() == Blockchain.Ethereum || card.getBlockchain() == Blockchain.EthereumTestNet) { + ETHRequestTask task = new ETHRequestTask(card.getBlockchain()); + Infura_Request req = Infura_Request.GetBalance(card.getWallet()); + req.setID(67); + req.setBlockchain(card.getBlockchain()); + Infura_Request reqNonce = Infura_Request.GetOutTransactionCount(card.getWallet()); + reqNonce.setID(67); + reqNonce.setBlockchain(card.getBlockchain()); + + task.execute(req, reqNonce); + + RateInfoTask taskRate = new RateInfoTask(); + ExchangeRequest rate = ExchangeRequest.GetRate(card.getWallet(), "ethereum", "ethereum"); + taskRate.execute(rate); + + } else if (card.getBlockchain() == Blockchain.BitcoinTestNet || card.getBlockchain() == Blockchain.Bitcoin) { + SharedData data = new SharedData(SharedData.COUNT_REQUEST); + for (int i = 0; i < data.allRequest; ++i) { + + String nodeAddress = engine.GetNextNode(card); + int nodePort = engine.GetNextNodePort(card); + RequestWalletInfoTask connectTaskEx = new RequestWalletInfoTask(nodeAddress, nodePort, data); + connectTaskEx.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, Electrum_Request.CheckBalance(card.getWallet())); + } + + String nodeAddress = engine.GetNode(card); + int nodePort = engine.GetNodePort(card); + RequestWalletInfoTask task = new RequestWalletInfoTask(nodeAddress, nodePort); + if( mSwipeRefreshLayout!=null ) mSwipeRefreshLayout.setRefreshing(true); //TODO: tmp + requestTasks.add(task); + task.execute(Electrum_Request.ListUnspent(card.getWallet()), Electrum_Request.ListHistory(card.getWallet())); + RateInfoTask taskRate = new RateInfoTask(); + ExchangeRequest rate = ExchangeRequest.GetRate(card.getWallet(), "bitcoin", "bitcoin"); + taskRate.execute(rate); + + } + else if (card.getBlockchain() == Blockchain.BitcoinCashTestNet || card.getBlockchain() == Blockchain.BitcoinCash) { + SharedData data = new SharedData(SharedData.COUNT_REQUEST); + for (int i = 0; i < data.allRequest; ++i) { + + String nodeAddress = engine.GetNextNode(card); + int nodePort = engine.GetNextNodePort(card); + RequestWalletInfoTask connectTaskEx = new RequestWalletInfoTask(nodeAddress, nodePort, data); + connectTaskEx.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, Electrum_Request.CheckBalance(card.getWallet())); + } + + String nodeAddress = engine.GetNode(card); + int nodePort = engine.GetNodePort(card); + RequestWalletInfoTask task = new RequestWalletInfoTask(nodeAddress, nodePort); + if( mSwipeRefreshLayout!=null ) mSwipeRefreshLayout.setRefreshing(true); //TODO: tmp + requestTasks.add(task); + task.execute(Electrum_Request.ListUnspent(card.getWallet()), Electrum_Request.ListHistory(card.getWallet())); + RateInfoTask taskRate = new RateInfoTask(); + ExchangeRequest rate = ExchangeRequest.GetRate(card.getWallet(), "bitcoin-cash", "bitcoin-cash"); + taskRate.execute(rate); + + }else if (card.getBlockchain() == Blockchain.Token) { + ETHRequestTask updateETH = new ETHRequestTask(card.getBlockchain()); + Infura_Request reqETH = Infura_Request.GetTokenBalance(card.getWallet(), engine.GetContractAddress(card), engine.GetTokenDecimals(card)); + reqETH.setID(67); + reqETH.setBlockchain(card.getBlockchain()); + + + Infura_Request reqBalance = Infura_Request.GetBalance(card.getWallet()); + reqBalance.setID(67); + reqBalance.setBlockchain(card.getBlockchain()); + + + RateInfoTask taskRate = new RateInfoTask(); + ExchangeRequest rate = ExchangeRequest.GetRate(card.getWallet(), "basic-attention-token", "ethereum"); + taskRate.execute(rate); + + Infura_Request reqNonce = Infura_Request.GetOutTransactionCount(card.getWallet()); + reqNonce.setID(67); + reqNonce.setBlockchain(card.getBlockchain()); + + updateETH.execute(reqETH, reqNonce, reqBalance); + } + } + + public void addCard(final Tangem_Card card) { + if (mCardListAdapter != null) { + getActivity().runOnUiThread(new Runnable() { + @Override + public void run() { + unsuccessReadCount = 0; + slCardUIDs.add(0, card.getUID()); + mCardListAdapter.addCard(card); + + CoinEngine engine = CoinEngineFactory.Create(card.getBlockchain()); + + if (card.getStatus() == Tangem_Card.Status.Loaded) { + + if (card.getBlockchain() == Blockchain.Ethereum || card.getBlockchain() == Blockchain.EthereumTestNet) { + ETHRequestTask task = new ETHRequestTask(card.getBlockchain()); + Infura_Request req = Infura_Request.GetBalance(card.getWallet()); + req.setID(67); + req.setBlockchain(card.getBlockchain()); + Infura_Request reqNonce = Infura_Request.GetOutTransactionCount(card.getWallet()); + reqNonce.setID(67); + reqNonce.setBlockchain(card.getBlockchain()); + + task.execute(req, reqNonce); + + RateInfoTask taskRate = new RateInfoTask(); + ExchangeRequest rate = ExchangeRequest.GetRate(card.getWallet(), "ethereum", "ethereum"); + taskRate.execute(rate); + + } else if (card.getBlockchain() == Blockchain.BitcoinTestNet || card.getBlockchain() == Blockchain.Bitcoin) { + SharedData data = new SharedData(SharedData.COUNT_REQUEST); + + for (int i = 0; i < data.allRequest; ++i) { + String nodeAddress = engine.GetNextNode(card); + int nodePort = engine.GetNextNodePort(card); + + RequestWalletInfoTask connectTaskEx = new RequestWalletInfoTask(nodeAddress, nodePort, data); + connectTaskEx.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, Electrum_Request.CheckBalance(card.getWallet())); + } + + String nodeAddress = engine.GetNode(card); + int nodePort = engine.GetNodePort(card); + + RequestWalletInfoTask task = new RequestWalletInfoTask(nodeAddress, nodePort); + if( mSwipeRefreshLayout!=null ) mSwipeRefreshLayout.setRefreshing(true); //TODO: tmp + + requestTasks.add(task); + task.execute(Electrum_Request.ListUnspent(card.getWallet()), Electrum_Request.ListHistory(card.getWallet())); + RateInfoTask taskRate = new RateInfoTask(); + ExchangeRequest rate = ExchangeRequest.GetRate(card.getWallet(), "bitcoin", "bitcoin"); + taskRate.execute(rate); + + }else if (card.getBlockchain() == Blockchain.BitcoinCashTestNet || card.getBlockchain() == Blockchain.BitcoinCash) { + SharedData data = new SharedData(SharedData.COUNT_REQUEST); + + for (int i = 0; i < data.allRequest; ++i) { + String nodeAddress = engine.GetNextNode(card); + int nodePort = engine.GetNextNodePort(card); + + RequestWalletInfoTask connectTaskEx = new RequestWalletInfoTask(nodeAddress, nodePort, data); + connectTaskEx.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, Electrum_Request.CheckBalance(card.getWallet())); + } + + String nodeAddress = engine.GetNode(card); + int nodePort = engine.GetNodePort(card); + + RequestWalletInfoTask task = new RequestWalletInfoTask(nodeAddress, nodePort); + if( mSwipeRefreshLayout!=null ) mSwipeRefreshLayout.setRefreshing(true); //TODO: tmp + + requestTasks.add(task); + task.execute(Electrum_Request.ListUnspent(card.getWallet()), Electrum_Request.ListHistory(card.getWallet())); + RateInfoTask taskRate = new RateInfoTask(); + ExchangeRequest rate = ExchangeRequest.GetRate(card.getWallet(), "bitcoin-cash", "bitcoin-cash"); + taskRate.execute(rate); + + } + else if (card.getBlockchain() == Blockchain.Token) { + ETHRequestTask updateETH = new ETHRequestTask(card.getBlockchain()); + Infura_Request reqETH = Infura_Request.GetTokenBalance(card.getWallet(), engine.GetContractAddress(card), engine.GetTokenDecimals(card)); + reqETH.setID(67); + reqETH.setBlockchain(card.getBlockchain()); + + + Infura_Request reqBalance = Infura_Request.GetBalance(card.getWallet()); + reqBalance.setID(67); + reqBalance.setBlockchain(card.getBlockchain()); + + + RateInfoTask taskRate = new RateInfoTask(); + ExchangeRequest rate = ExchangeRequest.GetRate(card.getWallet(), "basic-attention-token", "ethereum"); + taskRate.execute(rate); + + Infura_Request reqNonce = Infura_Request.GetOutTransactionCount(card.getWallet()); + reqNonce.setID(67); + reqNonce.setBlockchain(card.getBlockchain()); + + updateETH.execute(reqETH, reqNonce, reqBalance); + } + } + if (getActivity().getClass() == MainActivity.class) { + ((MainActivity) getActivity()).showCleanButton(); + } + } + }); + } + } + + @Override + public void onViewCard(Bundle cardInfo) { + + if( mSwipeRefreshLayout!=null ) mSwipeRefreshLayout.setRefreshing(false); //TODO: tmp + + String UID = cardInfo.getString("UID"); + Tangem_Card card = new Tangem_Card(UID); + card.LoadFromBundle(cardInfo.getBundle("Card")); + + + Intent intent; + + if (card.getStatus() == Tangem_Card.Status.Empty) { + intent = new Intent(getActivity(), EmptyWalletActivity.class); + } else if (card.getStatus() == Tangem_Card.Status.Loaded) { + intent = new Intent(getActivity(), LoadedWalletActivity.class); + } else if (card.getStatus() == Tangem_Card.Status.NotPersonalized || card.getStatus() == Tangem_Card.Status.Purged) { + return; + } else { + intent = new Intent(getActivity(), LoadedWalletActivity.class); + } + + intent.putExtras(cardInfo); + startActivityForResult(intent, REQUEST_CODE_SHOW_CARD_ACTIVITY); + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + Log.d(logTag, "ActivityResult: requestCode = " + requestCode + ", resultCode = " + resultCode); + // если пришло ОК + if (resultCode == Activity.RESULT_OK && requestCode == REQUEST_CODE_SHOW_CARD_ACTIVITY) { + if (data != null && data.getExtras().containsKey("UID")) { + final Tangem_Card card = new Tangem_Card(data.getStringExtra("UID")); + card.LoadFromBundle(data.getBundleExtra("Card")); + if (data.getStringExtra("modification").equals("delete")) { + mCardListAdapter.removeCard(card); + for (int i = 0; i < slCardUIDs.size(); i++) { + if (slCardUIDs.get(i).equals(card.getUID())) { + slCardUIDs.remove(i); + break; + } + } + if (mCardListAdapter.getItemCount() == 0 && getActivity().getClass() == MainActivity.class) { + ((MainActivity) getActivity()).hideCleanButton(); + } + } else if (data.getStringExtra("modification").equals("update")) { + mCardListAdapter.updateCard(card); + } else if (data.getStringExtra("modification").equals("updateAndViewCard")) { + mCardListAdapter.updateCard(card); + onViewCard(data.getExtras()); + } + + } + } else if (resultCode == Activity.RESULT_OK && requestCode == REQUEST_CODE_ENTER_PIN_ACTIVITY) { + if (lastTag != null) onTagDiscovered(lastTag); + } + } + + @Override + public void doClean() { + mCardListAdapter.clearCards(); + slCardUIDs.clear(); + for (RequestWalletInfoTask rt : requestTasks) { + rt.cancel(true); + } + lastRead_UID = ""; + } + + public void doEnterPIN() { + Intent intent = new Intent(getContext(), RequestPINActivity.class); + intent.putExtra("mode", RequestPINActivity.Mode.RequestPIN.toString()); + startActivityForResult(intent, REQUEST_CODE_ENTER_PIN_ACTIVITY); + } + + private static String lastRead_UID = ""; + private static ArrayList lastRead_UnsuccessfullPINs = new ArrayList<>(); + private static Tangem_Card.EncryptionMode lastRead_Encryption = null; + + private class ReadCardInfoTask extends Thread { + + + IsoDep mIsoDep; + CardProtocol.Notifications mNotifications; + private boolean isCancelled = false; + + ReadCardInfoTask(IsoDep isoDep, CardProtocol.Notifications notifications) { + mIsoDep = isoDep; + mNotifications = notifications; + } + + @Override + public void run() { + if (mIsoDep == null) { + return; + } + try { + // for Samsung's bugs - + // Workaround for the Samsung Galaxy S5 (since the + // first connection always hangs on transceive). + int timeout = mIsoDep.getTimeout(); + mIsoDep.connect(); + mIsoDep.close(); + mIsoDep.connect(); + mIsoDep.setTimeout(timeout); + try { + CardProtocol protocol = new CardProtocol(getContext(), mIsoDep, mNotifications); + mNotifications.OnReadStart(protocol); + try { + mNotifications.OnReadProgress(protocol, 5); + + byte[] UID = mIsoDep.getTag().getId(); + String sUID = Util.byteArrayToHexString(UID); + if (!lastRead_UID.equals(sUID)) { + lastRead_UID = sUID; + lastRead_UnsuccessfullPINs.clear(); + lastRead_Encryption = null; + } + + Log.i("ReadCardInfoTask", "[-- Start read card info --]"); + + if (isCancelled) return; + + protocol.setPIN(PINStorage.getDefaultPIN()); + protocol.clearReadResult(); + + if (lastRead_Encryption == null) { + Log.i("ReadCardInfoTask", "Try get supported encryption mode"); + protocol.run_GetSupportedEncryption(); + } else { + Log.i("ReadCardInfoTask", "Use already defined encryption mode: " + lastRead_Encryption.name()); + protocol.getCard().encryptionMode = lastRead_Encryption; + } + + if (protocol.haveReadResult()) { + //already have read result (obtained while get supported encryption), only read issuer data and define offline balance + protocol.parseReadResult(); + protocol.run_ReadOrWriteIssuerDataAndDefineOfflineBalance(); + mNotifications.OnReadProgress(protocol, 60); + PINStorage.setLastUsedPIN(protocol.getCard().getPIN()); + } else { + //don't have read result - may be don't get supported encryption on this try, need encryption or need another PIN + if (lastRead_Encryption == null) { + // we try get supported encryption on this time + lastRead_Encryption = protocol.getCard().encryptionMode; + if (protocol.getCard().encryptionMode == Tangem_Card.EncryptionMode.None) { + // default pin not accepted + lastRead_UnsuccessfullPINs.add(PINStorage.getDefaultPIN()); + } + } + + boolean pinFound = false; + for (String PIN : PINStorage.getPINs()) { + Log.e("ReadCardInfoTask", "PIN: " + PIN); + + boolean skipPin = false; + for (int i = 0; i < lastRead_UnsuccessfullPINs.size(); i++) { + if (lastRead_UnsuccessfullPINs.get(i).equals(PIN)) { + skipPin = true; + break; + } + } + + if (skipPin) { + Log.e("ReadCardInfoTask", "Skip PIN - already checked before"); + continue; + } + + try { + protocol.setPIN(PIN); + if (protocol.getCard().encryptionMode != Tangem_Card.EncryptionMode.None) { + protocol.CreateProtocolKey(); + } + protocol.run_Read(); + mNotifications.OnReadProgress(protocol, 60); + PINStorage.setLastUsedPIN(PIN); + pinFound = true; + protocol.getCard().setPIN(PIN); + break; + } catch (CardProtocol.TangemException_InvalidPIN e) { + Log.e(logTag, e.getMessage()); + lastRead_UnsuccessfullPINs.add(PIN); + } + } + if (!pinFound) { + throw new CardProtocol.TangemException_InvalidPIN("No valid PIN found!"); + } + } + + protocol.run_CheckPIN2isDefault(); + + } catch (Exception e) { + e.printStackTrace(); + protocol.setError(e); + + } finally { + Log.i("ReadCardInfoTask", "[-- Finish read card info --]"); + mNotifications.OnReadFinish(protocol); + } + } finally { + mNfcManager.IgnoreTag(mIsoDep.getTag()); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + void cancel(Boolean AllowInterrupt) { + try { + if (this.isAlive()) { + isCancelled = true; + join(500); + } + if (this.isAlive() && AllowInterrupt) { + interrupt(); + mNotifications.OnReadCancel(); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + } + + public void OnReadStart(CardProtocol cardProtocol) { + progressBar.post(new Runnable() { + @Override + public void run() { + progressBar.setVisibility(View.VISIBLE); + progressBar.setProgress(5); + } + }); + } + + public void OnReadFinish(final CardProtocol cardProtocol) { + + readCardInfoTask = null; + + if (cardProtocol != null) { + if (cardProtocol.getError() == null) { + progressBar.post(new Runnable() { + @Override + public void run() { + progressBar.setProgress(100); + progressBar.setProgressTintList(ColorStateList.valueOf(Color.GREEN)); + addCard(cardProtocol.getCard()); + } + }); + } else { + // remove last UIDs because of error and no card read + progressBar.post(new Runnable() { + @Override + public void run() { + Toast.makeText(getContext(), "Try to scan again", Toast.LENGTH_LONG).show(); + unsuccessReadCount++; + progressBar.setProgress(100); + progressBar.setProgressTintList(ColorStateList.valueOf(Color.RED)); + slCardUIDs.remove(cardProtocol.getCard().getUID()); + if (cardProtocol.getError() instanceof CardProtocol.TangemException_InvalidPIN) { + doEnterPIN(); + } else if (cardProtocol.getError() instanceof CardProtocol.TangemException_ExtendedLengthNotSupported) { + if (!NoExtendedLengthSupportDialog.allreadyShowed) { + new NoExtendedLengthSupportDialog().show(getActivity().getFragmentManager(), "NoExtendedLengthSupportDialog"); + } + } else { + lastTag = null; + } + } + }); + } + } + + progressBar.postDelayed(new Runnable() { + @Override + public void run() { + try { + progressBar.setProgress(0); + progressBar.setProgressTintList(ColorStateList.valueOf(Color.DKGRAY)); + progressBar.setVisibility(View.INVISIBLE); + } catch (Exception e) { + e.printStackTrace(); + } + } + }, 500); + } + + public void OnReadProgress(CardProtocol protocol, final int progress) { + progressBar.post(new Runnable() { + @Override + public void run() { + progressBar.setProgress(progress); + } + }); + } + + public void OnReadCancel() { + + readCardInfoTask = null; + + progressBar.postDelayed(new Runnable() { + @Override + public void run() { + try { + progressBar.setProgress(0); + progressBar.setProgressTintList(ColorStateList.valueOf(Color.DKGRAY)); + progressBar.setVisibility(View.INVISIBLE); + } catch (Exception e) { + e.printStackTrace(); + } + } + }, 500); + } + + + public void OnReadWait(final int msec) { + WaitSecurityDelayDialog.OnReadWait(getActivity(), msec); + } + + @Override + public void OnReadBeforeRequest(int timeout) { + WaitSecurityDelayDialog.onReadBeforeRequest(getActivity(), timeout); + } + + @Override + public void OnReadAfterRequest() { + WaitSecurityDelayDialog.onReadAfterRequest(getActivity()); + } + + private class RequestWalletInfoTask extends Electrum_Task { + public RequestWalletInfoTask(String host, int port) { + super(host, port); + } + + + public RequestWalletInfoTask(String host, int port, SharedData sharedData) { + super(host, port, sharedData); + } + + @Override + protected void onProgressUpdate(Integer... values) { + super.onProgressUpdate(values); + } + + @Override + protected void onCancelled() { + super.onCancelled(); + requestTasks.remove(this); + if ( mSwipeRefreshLayout!=null && requestTasks.size() == 0) mSwipeRefreshLayout.setRefreshing(false); //TODO: tmp + + } + + void FinishWithError(String wallet, String message) { + mCardListAdapter.UpdateWalletError(wallet, "Cannot obtain data from blockchain"); + } + + @Override + protected void onPostExecute(List requests) { + super.onPostExecute(requests); + requestTasks.remove(this); + Log.i("RequestWalletInfoTask", "onPostExecute[" + String.valueOf(requests.size()) + "]"); + + CoinEngine engine = CoinEngineFactory.Create(Blockchain.Bitcoin); + + for (Electrum_Request request : requests) { + try { + + if (request.error == null) { + if (request.isMethod(Electrum_Request.METHOD_GetBalance)) { + try { + Long conf = request.getResult().getLong("confirmed"); + Long unconf = request.getResult().getLong("unconfirmed"); + if (sharedCounter != null) { + int counter = sharedCounter.requestCounter.incrementAndGet(); + if (counter != 1) { + continue; + } + } + String mWalletAddress = request.getParams().getString(0); + mCardListAdapter.UpdateWalletBalance(mWalletAddress, conf, unconf, getValidationNodeDescription()); + } catch (JSONException e) { + if (sharedCounter != null) { + int errCounter = sharedCounter.errorRequest.incrementAndGet(); + if (errCounter == sharedCounter.allRequest) { + e.printStackTrace(); + FinishWithError(request.WalletAddress, e.toString()); + engine.SwitchNode(null); + } + } else { + e.printStackTrace(); + FinishWithError(request.WalletAddress, e.toString()); + engine.SwitchNode(null); + } + } + } else if (request.isMethod(Electrum_Request.METHOD_ListUnspent)) { + try { + String mWalletAddress = request.getParams().getString(0); + mCardListAdapter.UpdateWalletUnspent(mWalletAddress, request.getResultArray()); + + JSONArray unspentList = request.getResultArray(); + + for (int i = 0; i < unspentList.length(); i++) { + JSONObject jsUnspent = unspentList.getJSONObject(i); + Integer height = jsUnspent.getInt("height"); + String hash = jsUnspent.getString("tx_hash"); + if (height != -1) { + RequestWalletInfoTask task = new RequestWalletInfoTask(request.Host, request.Port); + requestTasks.add(task); + task.execute(Electrum_Request.GetHeader(mWalletAddress, String.valueOf(height)), + Electrum_Request.GetTransaction(mWalletAddress, hash)); + } + } + } catch (JSONException e) { + e.printStackTrace(); + FinishWithError(request.WalletAddress, e.toString()); + engine.SwitchNode(null); + } + } else if (request.isMethod(Electrum_Request.METHOD_GetHistory)) { + try { + String mWalletAddress = request.getParams().getString(0); + mCardListAdapter.UpdateWalletHistory(mWalletAddress, request.getResultArray()); + + JSONArray historyList = request.getResultArray(); + + for (int i = 0; i < historyList.length(); i++) { + JSONObject jsUnspent = historyList.getJSONObject(i); + Integer height = jsUnspent.getInt("height"); + String hash = jsUnspent.getString("tx_hash"); + if (height != -1) { + RequestWalletInfoTask task = new RequestWalletInfoTask(request.Host, request.Port); + requestTasks.add(task); + + task.execute(Electrum_Request.GetHeader(mWalletAddress, String.valueOf(height)), + Electrum_Request.GetTransaction(mWalletAddress, hash)); + } + } + } catch (JSONException e) { + e.printStackTrace(); + FinishWithError(request.WalletAddress, e.toString()); + engine.SwitchNode(null); + } + } else if (request.isMethod(Electrum_Request.METHOD_GetHeader)) { + try { + String mWalletAddress = request.WalletAddress; + + mCardListAdapter.UpdateWalletHeader(mWalletAddress, request.getResult()); + + } catch (JSONException e) { + e.printStackTrace(); + FinishWithError(request.WalletAddress, e.toString()); + engine.SwitchNode(null); + } + } else if (request.isMethod(Electrum_Request.METHOD_GetTransaction)) { + try { + Log.e("MainActivityFragment_TX", request.TxHash); + String mWalletAddress = request.WalletAddress; + String tx = request.TxHash; + String raw = request.getResultString(); + Log.e("MainActivityFragment_R", raw); + mCardListAdapter.UpdateTransaction(mWalletAddress, tx, raw); + + } catch (JSONException e) { + e.printStackTrace(); + mCardListAdapter.UpdateWalletError(request.WalletAddress, "Cannot obtain data from blockchain"); + FinishWithError(request.WalletAddress, e.toString()); + engine.SwitchNode(null); + } + } + } else { + if (sharedCounter != null) { + int errCounter = sharedCounter.errorRequest.incrementAndGet(); + if (errCounter >= sharedCounter.allRequest) { + FinishWithError(request.WalletAddress, request.error); + engine.SwitchNode(null); + } + } else { + FinishWithError(request.WalletAddress, request.error); + engine.SwitchNode(null); + } + + } + } catch (JSONException e) { + if (sharedCounter != null) { + int errCounter = sharedCounter.errorRequest.incrementAndGet(); + if (errCounter >= sharedCounter.allRequest) { + e.printStackTrace(); + mCardListAdapter.UpdateWalletError(request.WalletAddress, "Cannot obtain data from blockchain"); + engine.SwitchNode(null); + } + } else { + e.printStackTrace(); + mCardListAdapter.UpdateWalletError(request.WalletAddress, "Cannot obtain data from blockchain"); + engine.SwitchNode(null); + } + + } + } + + if ( mSwipeRefreshLayout!=null && requestTasks.size() == 0) mSwipeRefreshLayout.setRefreshing(false); //TODO: tmp + } + + } + + private class ETHRequestTask extends Infura_Task { + ETHRequestTask(Blockchain blockchain) { + super(blockchain); + } + + void FinishWithError(String wallet, String message) { + mCardListAdapter.UpdateWalletError(wallet, "Cannot obtain data from blockchain"); + } + + @Override + protected void onPostExecute(List requests) { + super.onPostExecute(requests); + for (Infura_Request request : requests) { + try { + if (request.error == null) { + + if (request.isMethod(Infura_Request.METHOD_ETH_GetBalance)) { + try { + String mWalletAddress = request.getParams().getString(0); + + String balanceCap = request.getResultString(); + balanceCap = balanceCap.substring(2); + BigInteger l = new BigInteger(balanceCap, 16); + BigInteger d = l.divide(new BigInteger("1000000000000000000", 10)); + Long balance = d.longValue(); + if (request.getBlockchain() != Blockchain.Token) + mCardListAdapter.UpdateWalletBalance(mWalletAddress, balance, l.toString(10), getValidationNodeDescription()); + mCardListAdapter.UpdateWalletBalanceOnlyAlter(mWalletAddress, l.toString(10)); + } catch (JSONException e) { + e.printStackTrace(); + FinishWithError(request.WalletAddress, e.toString()); + } + } else if (request.isMethod(Infura_Request.METHOD_ETH_Call)) { + try { + String mWalletAddress = request.WalletAddress; + + String balanceCap = request.getResultString(); + balanceCap = balanceCap.substring(2); + BigInteger l = new BigInteger(balanceCap, 16); + Long balance = l.longValue(); + if (l.compareTo(BigInteger.ZERO) == 0) { + mCardListAdapter.UpdateWalletBlockchain(mWalletAddress, Blockchain.Ethereum); + mCardListAdapter.AddWalletBlockchainNameToken(mWalletAddress); + Tangem_Card card = mCardListAdapter.getCardByWallet(request.WalletAddress); + if (card != null) { + refreshCard(card); + } + return; + } + mCardListAdapter.UpdateWalletBalance(mWalletAddress, balance, l.toString(10), getValidationNodeDescription()); + } catch (JSONException e) { + e.printStackTrace(); + FinishWithError(request.WalletAddress, e.toString()); + } catch (Exception e) { + e.printStackTrace(); + FinishWithError(request.WalletAddress, e.toString()); + } + } else if (request.isMethod(Infura_Request.METHOD_ETH_GetOutTransactionCount)) { + try { + String mWalletAddress = request.getParams().getString(0); + + String nonce = request.getResultString(); + nonce = nonce.substring(2); + BigInteger count = new BigInteger(nonce, 16); + + mCardListAdapter.UpdateWalletCoutConfirmTx(mWalletAddress, count); + } catch (JSONException e) { + e.printStackTrace(); + FinishWithError(request.WalletAddress, e.toString()); + } + } + } else { + FinishWithError(request.WalletAddress, request.error); + } + } catch (JSONException e) { + e.printStackTrace(); + FinishWithError(request.WalletAddress, e.toString()); + } + } + } + + } + + private class RateInfoTask extends ExchangeTask { + protected void onPostExecute(List requests) { + super.onPostExecute(requests); + for (ExchangeRequest request : requests) { + if (request.error == null) { + try { + + JSONArray arr = request.getAnswerList(); + for (int i = 0; i < arr.length(); ++i) { + JSONObject obj = arr.getJSONObject(i); + String currency = obj.getString("id"); + + boolean stop = false; + boolean stopAlter = false; + if (currency.equals(request.currency)) { + String usd = obj.getString("price_usd"); + + Float rate = Float.valueOf(usd); + mCardListAdapter.UpdateRate(request.WalletAddress, rate); + stop = true; + } + + if (currency.equals(request.currencyAlter)) { + String usd = obj.getString("price_usd"); + + Float rate = Float.valueOf(usd); + mCardListAdapter.UpdateRateAlter(request.WalletAddress, rate); + stopAlter = true; + } + + if (stop && stopAlter) { + break; + } + } + + + //mCardListAdapter.UpdateWalletBalance(mWalletAddress, balance, l.toString(10)); + } catch (JSONException e) { + e.printStackTrace(); + } + } + } + } + } + + +} diff --git a/app/src/main/java/com/tangem/wallet/Manufacturer.java b/app/src/main/java/com/tangem/wallet/Manufacturer.java new file mode 100644 index 0000000000..c904f9b308 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/Manufacturer.java @@ -0,0 +1,126 @@ +package com.tangem.wallet; + +import android.util.Log; + +import org.spongycastle.asn1.ASN1EncodableVector; +import org.spongycastle.asn1.ASN1Integer; +import org.spongycastle.asn1.DERSequence; +import org.spongycastle.jce.ECNamedCurveTable; +import org.spongycastle.jce.spec.ECNamedCurveParameterSpec; +import org.spongycastle.jce.spec.ECPublicKeySpec; +import org.spongycastle.math.ec.ECPoint; + +import java.math.BigInteger; +import java.security.KeyFactory; +import java.security.PublicKey; +import java.util.Arrays; + +/** + * Created by dvol on 09.08.2017. + */ + +public enum Manufacturer { +// Unknown("", "Unknown", new byte[]{}), +// SMARTCASH_AG("SMART CASH AG","SMART CASH AG", +// new byte[]{0x04, +// (byte) 0x4F, (byte) 0x53, (byte) 0x90, (byte) 0x2D, (byte) 0x50, (byte) 0xE2, (byte) 0xBB, (byte) 0x16, +// (byte) 0xD3, (byte) 0xDD, (byte) 0xC7, (byte) 0xA2, (byte) 0x03, (byte) 0x97, (byte) 0x28, (byte) 0x5E, +// (byte) 0x94, (byte) 0x21, (byte) 0x53, (byte) 0x69, (byte) 0x59, (byte) 0x8C, (byte) 0xE4, (byte) 0xDD, +// (byte) 0x62, (byte) 0x42, (byte) 0xDD, (byte) 0xB4, (byte) 0x5B, (byte) 0x96, (byte) 0xA1, (byte) 0x03, +// (byte) 0x1A, (byte) 0xF5, (byte) 0xC9, (byte) 0x73, (byte) 0x94, (byte) 0xC6, (byte) 0xF9, (byte) 0xC8, +// (byte) 0xD7, (byte) 0x6F, (byte) 0x38, (byte) 0xF9, (byte) 0x65, (byte) 0xCB, (byte) 0xA8, (byte) 0xAE, +// (byte) 0x85, (byte) 0xAF, (byte) 0xF7, (byte) 0x68, (byte) 0x55, (byte) 0xDC, (byte) 0xAA, (byte) 0x08, +// (byte) 0xF3, (byte) 0xCD, (byte) 0x15, (byte) 0x43, (byte) 0x04, (byte) 0x19, (byte) 0xF4, (byte) 0x49}), +// DEVELOPERS_SMARTCASH_AG("DEVELOP CASH AG","SMART CASH AG (DEVELOPERS)", +// new byte[]{0x04, +// (byte) 0xBA, (byte) 0xB8, (byte) 0x6D, (byte) 0x56, (byte) 0x29, (byte) 0x8C, (byte) 0x99, (byte) 0x6F, +// (byte) 0x56, (byte) 0x4A, (byte) 0x84, (byte) 0xFC, (byte) 0x88, (byte) 0xE2, (byte) 0x8A, (byte) 0xED, +// (byte) 0x38, (byte) 0x18, (byte) 0x4B, (byte) 0x12, (byte) 0xF0, (byte) 0x7E, (byte) 0x51, (byte) 0x91, +// (byte) 0x13, (byte) 0xBE, (byte) 0xF4, (byte) 0x8C, (byte) 0x76, (byte) 0xF3, (byte) 0xDF, (byte) 0x3A, +// +// (byte) 0xDC, (byte) 0x30, (byte) 0x35, (byte) 0x99, (byte) 0xB0, (byte) 0x8A, (byte) 0xC0, (byte) 0x5B, +// (byte) 0x55, (byte) 0xEC, (byte) 0x3D, (byte) 0xF9, (byte) 0x8D, (byte) 0x93, (byte) 0x38, (byte) 0x57, +// (byte) 0x3A, (byte) 0x62, (byte) 0x42, (byte) 0xF7, (byte) 0x6F, (byte) 0x5D, (byte) 0x28, (byte) 0xF4, +// (byte) 0xF0, (byte) 0xF3, (byte) 0x64, (byte) 0xE8, (byte) 0x7E, (byte) 0x8F, (byte) 0xCA, (byte) 0x2F}); + + + Unknown("", "Unknown"), + SMARTCASH_AG("SMART CASH AG", "SMART CASH AG"), + DEVELOPERS_SMARTCASH_AG("DEVELOP CASH AG", "SMART CASH AG (DEVELOPERS)"), + SMARTCASH("SMART CASH", "SMART CASH"); + + private String ID; + private String officialName; +// private byte[] publicKey; + + // Manufacturer(String id, String officialName, byte[] publicKey) { + Manufacturer(String id, String officialName) { + this.ID = id; + this.officialName = officialName; +// this.publicKey = publicKey; + } + + public String getOfficialName() { + return officialName; + } + +// public boolean VerifySignature(byte[] Challenge, byte[] Salt, byte[] Signature) { +// +// if (publicKey == null || Challenge == null || Salt == null || Signature == null) { +// Log.e(getOfficialName(), "Not all data read, can't check signature!"); +// return false; +// } +// try { +// java.security.Signature signature = java.security.Signature.getInstance("SHA256withECDSA"); +// ECNamedCurveParameterSpec spec = ECNamedCurveTable.getParameterSpec("secp256k1"); +// KeyFactory factory = KeyFactory.getInstance("EC", "SC"); +// +// ECPoint p1 = spec.getCurve().decodePoint(publicKey); +// +// ECPublicKeySpec keySpec = new ECPublicKeySpec(p1, spec); +// +// PublicKey publicKey = factory.generatePublic(keySpec); +// signature.initVerify(publicKey); +// signature.update(Challenge); +// signature.update(Salt); +// +// ASN1EncodableVector v = new ASN1EncodableVector(); +// int size = Signature.length / 2; +// v.add(/*r*/new ASN1Integer(new BigInteger(1, Arrays.copyOfRange(Signature, 0, size)))); +// v.add(/*s*/new ASN1Integer(new BigInteger(1, Arrays.copyOfRange(Signature, size, size * 2)))); +// byte[] sigDer = new DERSequence(v).getEncoded(); +// +// if (signature.verify(sigDer)) { +// Log.i(getOfficialName(), "Signature verification OK"); +// return true; +// } else { +// Log.e(getOfficialName(), "Signature verification failed"); +// } +// } +// catch (Exception e) +// { +// e.printStackTrace(); +// } +// return false; +// } +// +// public static Manufacturer FindManufacturer(String ID, byte[] Challenge, byte[] Salt, byte[] Signature) { +// Manufacturer[] manufacturers = Manufacturer.values(); +// for (int i = 1; i < manufacturers.length; i++) { +// if ( manufacturers[i].ID.equals(ID) && manufacturers[i].VerifySignature(Challenge, Salt, Signature)) { +// return manufacturers[i]; +// } +// } +// return Manufacturer.Unknown; +// } + + public static Manufacturer FindManufacturer(String ID) { + Manufacturer[] manufacturers = Manufacturer.values(); + for (int i = 1; i < manufacturers.length; i++) { + if (manufacturers[i].ID.equals(ID)) { + return manufacturers[i]; + } + } + return Manufacturer.Unknown; + } +} diff --git a/app/src/main/java/com/tangem/wallet/NoExtendedLengthSupportDialog.java b/app/src/main/java/com/tangem/wallet/NoExtendedLengthSupportDialog.java new file mode 100644 index 0000000000..d5217286e0 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/NoExtendedLengthSupportDialog.java @@ -0,0 +1,34 @@ +package com.tangem.wallet; + +import android.app.AlertDialog; +import android.app.Dialog; +import android.app.DialogFragment; +import android.content.DialogInterface; +import android.os.Bundle; + +public class NoExtendedLengthSupportDialog extends DialogFragment { + + public static boolean allreadyShowed=false; + + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + + return new AlertDialog.Builder(getActivity()) + .setIcon(R.drawable.tangem_logo_small_new) + .setTitle("Warning") + .setMessage("The NFC adapter of the device does not support extended length APDU, it's possible that some functions will not work!") + .setPositiveButton("Got it", + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int whichButton) { + NoExtendedLengthSupportDialog.allreadyShowed=true; + } + } + ) + .create(); + } + + @Override + public void onCancel(DialogInterface dialog) { + super.onCancel(dialog); + } +} diff --git a/app/src/main/java/com/tangem/wallet/PINStorage.java b/app/src/main/java/com/tangem/wallet/PINStorage.java new file mode 100644 index 0000000000..b1e376d3aa --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/PINStorage.java @@ -0,0 +1,206 @@ +package com.tangem.wallet; + +import android.content.Context; +import android.content.SharedPreferences; +import android.preference.PreferenceManager; +import android.util.Base64; +import android.util.Log; + +import com.tangem.cardReader.CardProtocol; + +import java.util.ArrayList; +import java.util.List; + +import javax.crypto.Cipher; + +/** + * Created by dvol on 12.09.2017. + * Global PIN Storage + */ + +class PINStorage { + private static String mSavedPIN, mUserPIN, mLastUsedPIN, mEncryptedPIN, mPIN2; + private static SharedPreferences sharedPreferences=null; + + static void Init(Context context) { + sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context); + mSavedPIN = sharedPreferences.getString("SavedPIN", null); + mUserPIN = null; + mLastUsedPIN = null; + mEncryptedPIN = null; + mPIN2 = null; + } + + + static List getPINs() { + ArrayList result = new ArrayList<>(); + if (mLastUsedPIN != null) result.add(mLastUsedPIN); + if (mEncryptedPIN != null && !result.contains(mEncryptedPIN)) result.add(mEncryptedPIN); + if (mUserPIN != null && !result.contains(mUserPIN)) result.add(mUserPIN); + if (mSavedPIN != null && !result.contains(mSavedPIN)) result.add(mSavedPIN); + if (!result.contains(CardProtocol.DefaultPIN)) result.add(CardProtocol.DefaultPIN); + return result; + } + + static void setLastUsedPIN(String PIN) { + mLastUsedPIN = PIN; + } + + static void setUserPIN(String PIN) { + mUserPIN = PIN; + } + + static void setPIN2(String PIN) { + mPIN2 = PIN; + } + + static void savePIN(String PIN) { + mSavedPIN = PIN; + if (mSavedPIN != null && !mSavedPIN.isEmpty()) { + SharedPreferences.Editor editor = sharedPreferences.edit(); + editor.putString("SavedPIN", mSavedPIN); + editor.apply(); + } else { + deletePIN(); + } + } + + static void deletePIN() { + SharedPreferences.Editor editor = sharedPreferences.edit(); + if (mSavedPIN != null && mLastUsedPIN != null && mSavedPIN.equals(mLastUsedPIN)) { + mLastUsedPIN = null; + } + mSavedPIN = null; + editor.remove("SavedPIN"); + editor.apply(); + } + + static void saveEncryptedPIN(Cipher cipher, String PIN) { + try { + byte[] iv = cipher.getIV(); + byte[] bytes = cipher.doFinal(PIN.getBytes()); + String encryptedPIN = Base64.encodeToString(bytes, Base64.NO_WRAP); + String sIV = Base64.encodeToString(iv, Base64.NO_WRAP); + +// Log.d("PINStorage", String.format("saveEncryptedPIN: %s, encrypted: %s, iv: %s",PIN,encryptedPIN,sIV)); + SharedPreferences.Editor editor = sharedPreferences.edit(); + editor.putString("EncryptedPIN", encryptedPIN); + editor.putString("EncryptedIV", sIV); + editor.apply(); + + } catch (Exception e) { + e.printStackTrace(); + } + } + + static byte[] loadEncryptedIV() { + String sIV = sharedPreferences.getString("EncryptedIV", ""); +// Log.d("PINStorage", String.format("loadEncryptedIV: %s",sIV)); + + return Base64.decode(sIV, Base64.NO_WRAP); + } + + static String loadEncryptedPIN(Cipher cipher) { + String encryptedPIN = sharedPreferences.getString("EncryptedPIN", null); + + try { + byte[] bytes = Base64.decode(encryptedPIN, Base64.NO_WRAP); + mEncryptedPIN = new String(cipher.doFinal(bytes)); +// Log.d("PINStorage", String.format("loadEncryptedPIN: %s (encrypted: %s)",mEncryptedPIN,encryptedPIN)); + } catch (Exception e) { + e.printStackTrace(); + mEncryptedPIN = null; + } + return mEncryptedPIN; + } + + static boolean haveEncryptedPIN() { + return sharedPreferences.getString("EncryptedPIN", null) != null; + } + + static void deleteEncryptedPIN() { + if (mEncryptedPIN != null && mLastUsedPIN != null && mEncryptedPIN.equals(mLastUsedPIN)) { + mLastUsedPIN = null; + } + mEncryptedPIN = null; + SharedPreferences.Editor editor = sharedPreferences.edit(); + editor.remove("EncryptedPIN"); + editor.remove("EncryptedIV"); + editor.apply(); + } + + static void saveEncryptedPIN2(Cipher cipher, String PIN) { + try { + byte[] iv = cipher.getIV(); + byte[] bytes = cipher.doFinal(PIN.getBytes()); + String encryptedPIN = Base64.encodeToString(bytes, Base64.NO_WRAP); + String sIV = Base64.encodeToString(iv, Base64.NO_WRAP); + +// Log.d("PINStorage", String.format("saveEncryptedPIN: %s, encrypted: %s, iv: %s",PIN,encryptedPIN,sIV)); + SharedPreferences.Editor editor = sharedPreferences.edit(); + editor.putString("EncryptedPIN2", encryptedPIN); + editor.putString("EncryptedIV2", sIV); + editor.apply(); + + } catch (Exception e) { + e.printStackTrace(); + } + } + + static byte[] loadEncryptedIV2() { + String sIV = sharedPreferences.getString("EncryptedIV2", ""); +// Log.d("PINStorage", String.format("loadEncryptedIV: %s",sIV)); + + return Base64.decode(sIV, Base64.NO_WRAP); + } + + static String loadEncryptedPIN2(Cipher cipher) { + String encryptedPIN = sharedPreferences.getString("EncryptedPIN2", null); + + try { + byte[] bytes = Base64.decode(encryptedPIN, Base64.NO_WRAP); + mPIN2 = new String(cipher.doFinal(bytes)); +// Log.d("PINStorage", String.format("loadEncryptedPIN: %s (encrypted: %s)",mEncryptedPIN,encryptedPIN)); + } catch (Exception e) { + e.printStackTrace(); + mPIN2 = null; + } + return mPIN2; + } + + static boolean haveEncryptedPIN2() { + return sharedPreferences.getString("EncryptedPIN2", null) != null; + } + + static void deleteEncryptedPIN2() { + mPIN2 = null; + SharedPreferences.Editor editor = sharedPreferences.edit(); + editor.remove("EncryptedPIN2"); + editor.remove("EncryptedIV2"); + editor.apply(); + } + + public static String getPIN2() { + return mPIN2; + } + + public static boolean isDefaultPIN(String pin) { + return (pin != null) && (CardProtocol.DefaultPIN.equals(pin)); + } + + public static boolean isDefaultPIN2(String pin2) { + return (pin2 != null) && (CardProtocol.DefaultPIN2.equals(pin2)); + } + + public static String getDefaultPIN() { + return CardProtocol.DefaultPIN; + } + + public static String getDefaultPIN2() { + return CardProtocol.DefaultPIN2; + } + + public static boolean needInit() { + return sharedPreferences==null; + } +} diff --git a/app/src/main/java/com/tangem/wallet/PhoneUtility.java b/app/src/main/java/com/tangem/wallet/PhoneUtility.java new file mode 100644 index 0000000000..617286e30c --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/PhoneUtility.java @@ -0,0 +1,35 @@ +package com.tangem.wallet; + +import android.os.Build; + +/** + * Created by Ilia on 20.04.2018. + */ + +public class PhoneUtility { + public static String GetPhoneName() + { + return DeviceName.getDeviceName(); + + } + + public static String getDeviceInfo() { + StringBuilder stringBuilder = new StringBuilder(); + + stringBuilder.append("----------------------------------------\n"); + stringBuilder.append("MODEL: ").append(Build.MODEL).append("\n"); + stringBuilder.append("ID: ").append(Build.ID).append("\n"); + stringBuilder.append("Manufacturer: ").append(Build.MANUFACTURER).append("\n"); + stringBuilder.append("Brand: ").append(Build.BRAND).append("\n"); + stringBuilder.append("Hardware: ").append(Build.HARDWARE).append("\n"); + stringBuilder.append("Version: ").append(Build.VERSION.RELEASE).append(", ").append(Build.VERSION.INCREMENTAL).append("\n"); + stringBuilder.append("OS: ").append(Build.VERSION.BASE_OS).append("\n"); + stringBuilder.append("SDK: ").append(Build.VERSION.SDK_INT).append("\n"); + stringBuilder.append("BOARD: ").append(Build.BOARD).append("\n"); + stringBuilder.append("FINGERPRINT: ").append(Build.FINGERPRINT).append("\n"); + stringBuilder.append("----------------------------------------\n"); + + return stringBuilder.toString(); + + } +} diff --git a/app/src/main/java/com/tangem/wallet/PreparePaymentActivity.java b/app/src/main/java/com/tangem/wallet/PreparePaymentActivity.java new file mode 100644 index 0000000000..886f711168 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/PreparePaymentActivity.java @@ -0,0 +1,237 @@ +package com.tangem.wallet; + +import android.app.Activity; +import android.content.Intent; +import android.nfc.NfcAdapter; +import android.nfc.Tag; +import android.os.Bundle; +import android.support.v7.app.AppCompatActivity; +import android.text.Editable; +import android.text.Html; +import android.text.Spanned; +import android.text.TextWatcher; +import android.util.Log; +import android.view.View; +import android.widget.Button; +import android.widget.EditText; +import android.widget.ImageView; +import android.widget.TextView; + +import com.tangem.cardReader.NfcManager; + +import java.io.IOException; + + +public class PreparePaymentActivity extends AppCompatActivity implements NfcAdapter.ReaderCallback { + + private static final int REQUEST_CODE_SCAN_QR = 1; + private static final int REQUEST_CODE_SEND_PAYMENT = 2; + Button btnVerify; + EditText etWallet; + EditText etAmount; + TextView tvCurrency; + TextView tvCardId, tvBalance, tvBalanceEquivalent, tvAmountEquivalent; + ImageView ivCamera; + boolean use_mCurrency; + Tangem_Card mCard; + private NfcManager mNfcManager; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_prepare_payment); + + MainActivity.commonInit(getApplicationContext()); + + mNfcManager = new NfcManager(this, this); + + mCard = new Tangem_Card(getIntent().getStringExtra("UID")); + mCard.LoadFromBundle(getIntent().getExtras().getBundle("Card")); + + btnVerify = (Button) findViewById(R.id.btnVerify); + etWallet = (EditText) findViewById(R.id.etWallet); + etAmount = (EditText) findViewById(R.id.etAmount); + ivCamera = (ImageView) findViewById(R.id.ivCamera); + tvCurrency = (TextView) findViewById(R.id.tvCurrency); + tvCardId = (TextView) findViewById(R.id.tvCardID); + tvBalance = (TextView) findViewById(R.id.tvBalance); + tvBalanceEquivalent = (TextView) findViewById(R.id.tvBalanceEquivalent); + tvAmountEquivalent = (TextView) findViewById(R.id.tvAmountEquivalent); + + tvCardId.setText(mCard.getCIDDescription()); + CoinEngine engine = CoinEngineFactory.Create(mCard.getBlockchain()); + + if (mCard.getBlockchain() == Blockchain.Token) { + Spanned html = Html.fromHtml(engine.GetBalanceWithAlter(mCard)); + tvBalance.setText(html); + } else { + tvBalance.setText(engine.GetBalanceWithAlter(mCard)); + } + + tvBalanceEquivalent.setText(engine.GetBalanceEquivalent(mCard)); + + if(etAmount != null && mCard.getRemainingSignatures()<2) + { + etAmount.setEnabled(false); + } + + if( !mCard.getAmountEquivalentDescriptionAvailable()) + { + tvBalanceEquivalent.setError("Service unavailable"); + }else{ + tvBalanceEquivalent.setError(null); + } + + etAmount.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + try { + CoinEngine engine = CoinEngineFactory.Create(mCard.getBlockchain()); + tvAmountEquivalent.setText(engine.GetAmountEqualentDescriptor(mCard, etAmount.getText().toString())); + if (!mCard.getAmountEquivalentDescriptionAvailable()) { + tvAmountEquivalent.setError("Service unavailable"); + }else{ + tvAmountEquivalent.setError(null); + } + } catch (Exception e) { + e.printStackTrace(); + tvAmountEquivalent.setText(""); + } + } + + @Override + public void afterTextChanged(Editable s) { + + } + }); + + if(mCard.getBlockchain() == Blockchain.Ethereum || mCard.getBlockchain() == Blockchain.EthereumTestNet) + { + tvCurrency.setText(engine.GetBalanceCurrency(mCard)); + use_mCurrency=false; + etAmount.setText(engine.GetBalanceValue(mCard)); + } + else if(mCard.getBlockchain() == Blockchain.Bitcoin || mCard.getBlockchain() == Blockchain.BitcoinTestNet) + { + Double balance = engine.GetBalanceLong(mCard) / (mCard.getBlockchain().getMultiplier() / 1000.0); + tvCurrency.setText("m" + mCard.getBlockchain().getCurrency()); + use_mCurrency=true; + String output = FormatUtil.DoubleToString(balance); + etAmount.setText(output); + } + else if(mCard.getBlockchain() == Blockchain.BitcoinCash || mCard.getBlockchain() == Blockchain.BitcoinCashTestNet) + { + Double balance = engine.GetBalanceLong(mCard) / (mCard.getBlockchain().getMultiplier() / 1000.0); + tvCurrency.setText("m" + mCard.getBlockchain().getCurrency()); + use_mCurrency=true; + String output = FormatUtil.DoubleToString(balance); + etAmount.setText(output); + } + else + { + tvCurrency.setText(engine.GetBalanceCurrency(mCard)); + use_mCurrency=false; + etAmount.setText(engine.GetBalanceValue(mCard)); + } + + btnVerify.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + String strAmount; + strAmount=etAmount.getText().toString(); + CoinEngine engine = CoinEngineFactory.Create(mCard.getBlockchain()); + try { + if(!engine.CheckAmount(mCard, etAmount.getText().toString())) + { + etAmount.setError("Not enough funds on your card"); + } + } + catch(Exception e) + { + etAmount.setError("Unknown amount format"); + return; + } + + boolean checkAddress = engine.ValdateAddress(etWallet.getText().toString(), mCard); + if(!checkAddress) + { + etWallet.setError("Incorrect destination wallet address"); + return; + } + + if(etWallet.getText().toString().equals(mCard.getWallet())) + { + etWallet.setError("Destination wallet address equal source address"); + return; + } + + Intent intent = new Intent(getBaseContext(), ConfirmPaymentActivity.class); + intent.putExtra("UID", mCard.getUID()); + intent.putExtra("Card", mCard.getAsBundle()); + intent.putExtra("Wallet", etWallet.getText().toString()); + intent.putExtra("Amount", strAmount); + startActivityForResult(intent, REQUEST_CODE_SEND_PAYMENT); + } + }); + + ivCamera.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Intent intent = new Intent(getBaseContext(), QRScanActivity.class); + startActivityForResult(intent, REQUEST_CODE_SCAN_QR); + } + }); + + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (requestCode == REQUEST_CODE_SCAN_QR && resultCode == Activity.RESULT_OK && data != null && data.getExtras().containsKey("QRCode")) { + String code = data.getStringExtra("QRCode"); + if(code.contains("bitcoin:")) + { + String tmp[] = code.split("bitcoin:"); + code = tmp[1]; + } + etWallet.setText(code); + }else if (requestCode == REQUEST_CODE_SEND_PAYMENT ) { + + setResult(resultCode,data); + finish(); + } + } + + @Override + public void onTagDiscovered(Tag tag) { + try { + Log.w(getClass().getName(),"Ignore discovered tag!"); + mNfcManager.IgnoreTag(tag); + } catch (IOException e) { + e.printStackTrace(); + } + } + + @Override + public void onResume() { + super.onResume(); + mNfcManager.onResume(); + } + + @Override + public void onPause() { + super.onPause(); + mNfcManager.onPause(); + } + + @Override + public void onStop() { + super.onStop(); + mNfcManager.onStop(); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/wallet/PurgeActivity.java b/app/src/main/java/com/tangem/wallet/PurgeActivity.java new file mode 100644 index 0000000000..1f53a0a54c --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/PurgeActivity.java @@ -0,0 +1,333 @@ +package com.tangem.wallet; + +import android.app.Activity; +import android.content.Intent; +import android.content.res.ColorStateList; +import android.graphics.Color; +import android.nfc.NfcAdapter; +import android.nfc.Tag; +import android.nfc.tech.IsoDep; +import android.os.Bundle; +import android.support.v7.app.AppCompatActivity; +import android.util.Log; +import android.view.View; +import android.widget.ProgressBar; +import android.widget.TextView; +import android.widget.Toast; + +import com.tangem.cardReader.CardProtocol; +import com.tangem.cardReader.NfcManager; +import com.tangem.cardReader.Util; + +public class PurgeActivity extends AppCompatActivity implements NfcAdapter.ReaderCallback, CardProtocol.Notifications { + + public static final int RESULT_INVALID_PIN = Activity.RESULT_FIRST_USER; + + private Tangem_Card mCard; + private TextView tvCardID; + private NfcManager mNfcManager; + private static final String logTag = "Purge"; + private ProgressBar progressBar; + private PurgeTask purgeTask; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_purge); + + MainActivity.commonInit(getApplicationContext()); + + mCard = new Tangem_Card(getIntent().getStringExtra("UID")); + mCard.LoadFromBundle(getIntent().getExtras().getBundle("Card")); + + tvCardID = (TextView) findViewById(R.id.tvCardID); + tvCardID.setText(mCard.getCIDDescription()); + + mNfcManager = new NfcManager(this, this); + + progressBar = (ProgressBar) findViewById(R.id.progressBar); + progressBar.setProgressTintList(ColorStateList.valueOf(Color.DKGRAY)); + progressBar.setVisibility(View.INVISIBLE); + } + + @Override + public void onTagDiscovered(Tag tag) { + try { + // get IsoDep handle and run cardReader thread + final IsoDep isoDep = IsoDep.get(tag); + if (isoDep == null) { + throw new CardProtocol.TangemException(getString(R.string.wrong_tag_err)); + } + byte UID[] = tag.getId(); + String sUID = Util.byteArrayToHexString(UID); + Log.v(logTag, "UID: " + sUID); + + if (sUID.equals(mCard.getUID())) { + isoDep.setTimeout(mCard.getPauseBeforePIN2() + 65000); + purgeTask = new PurgeTask(isoDep, this); + + purgeTask.start(); + } else { + Log.d(logTag, "Mismatch card UID (" + sUID + " instead of " + mCard.getUID() + ")"); + mNfcManager.IgnoreTag(isoDep.getTag()); + return; + } + + } catch (Exception e) { + e.printStackTrace(); + } + + } + + @Override + public void onResume() { + super.onResume(); + mNfcManager.onResume(); + } + + @Override + public void onPause() { + mNfcManager.onPause(); + if (purgeTask != null) { + purgeTask.cancel(true); + } + super.onPause(); + } + + @Override + public void onStop() { + // dismiss enable NFC dialog + mNfcManager.onStop(); + if (purgeTask != null) { + purgeTask.cancel(true); + } + super.onStop(); + } + +// @Override +// public Dialog CreateNFCDialog(int id, AlertDialogWrapper.Builder builder, LayoutInflater li) { +// return mNfcManager.onCreateDialog(id, builder, li); +// } + + private class PurgeTask extends Thread { + + + private String txOutAddress; + + IsoDep mIsoDep; + CardProtocol.Notifications mNotifications; + private boolean isCancelled = false; + + public PurgeTask(IsoDep isoDep, CardProtocol.Notifications notifications) { + mIsoDep = isoDep; + mNotifications = notifications; + } + + @Override + public void run() { + if (mIsoDep == null) { + return; + } + CardProtocol protocol = new CardProtocol(getBaseContext(), mIsoDep, mCard, mNotifications); + + mNotifications.OnReadStart(protocol); + try { + + // for Samsung's bugs - + // Workaround for the Samsung Galaxy S5 (since the + // first connection always hangs on transceive). + int timeout = mIsoDep.getTimeout(); + mIsoDep.connect(); + mIsoDep.close(); + mIsoDep.connect(); + mIsoDep.setTimeout(timeout); + try { + + mNotifications.OnReadProgress(protocol, 5); + + Log.i("PurgeTask", "[-- Start purge --]"); + + if (isCancelled) return; + + if (mCard.getPauseBeforePIN2() > 0) { + mNotifications.OnReadWait(mCard.getPauseBeforePIN2()); + } + +// try { + protocol.run_PurgeWallet(PINStorage.getPIN2()); +// } finally { +// mNotifications.OnReadWait(0); +// } + + mNotifications.OnReadProgress(protocol, 50); + + protocol.run_Read(); + + mNotifications.OnReadProgress(protocol, 100); + + if (isCancelled) return; + + } finally { + mNfcManager.IgnoreTag(mIsoDep.getTag()); + } + } catch (Exception e) { + e.printStackTrace(); + protocol.setError(e); + + } finally { + Log.i("PurgeTask", "[-- Finish purge --]"); + mNotifications.OnReadFinish(protocol); + } + } + + public void cancel(Boolean AllowInterrupt) { + try { + if (this.isAlive()) { + isCancelled = true; + join(500); + } + if (this.isAlive() && AllowInterrupt) { + interrupt(); + mNotifications.OnReadCancel(); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + } + + + public void OnReadStart(CardProtocol cardProtocol) { + progressBar.post(new Runnable() { + @Override + public void run() { + progressBar.setVisibility(View.VISIBLE); + progressBar.setProgress(5); + } + }); + } + + public void OnReadFinish(final CardProtocol cardProtocol) { + + purgeTask = null; + + if (cardProtocol != null) { + if (cardProtocol.getError() == null) { + progressBar.post(new Runnable() { + @Override + public void run() { + progressBar.setProgress(100); + progressBar.setProgressTintList(ColorStateList.valueOf(Color.GREEN)); + Intent intent = new Intent(); + intent.putExtra("UID", cardProtocol.getCard().getUID()); + intent.putExtra("Card", cardProtocol.getCard().getAsBundle()); + setResult(Activity.RESULT_OK, intent); + finish(); + } + }); + } else { + if (cardProtocol.getError() instanceof CardProtocol.TangemException_InvalidPIN) { + progressBar.post(new Runnable() { + @Override + public void run() { + progressBar.setProgress(100); + progressBar.setProgressTintList(ColorStateList.valueOf(Color.RED)); + } + }); + progressBar.postDelayed(new Runnable() { + @Override + public void run() { + try { + progressBar.setProgress(0); + progressBar.setProgressTintList(ColorStateList.valueOf(Color.DKGRAY)); + progressBar.setVisibility(View.INVISIBLE); + Intent intent = new Intent(); + intent.putExtra("UID", cardProtocol.getCard().getUID()); + intent.putExtra("Card", cardProtocol.getCard().getAsBundle()); + intent.putExtra("message", "Cannot erase wallet. Make sure you enter correct PIN2!"); + setResult(RESULT_INVALID_PIN, intent); + finish(); + } catch (Exception e) { + e.printStackTrace(); + } + } + }, 500); + return; + } else { + progressBar.post(new Runnable() { + @Override + public void run() { + if (cardProtocol.getError() instanceof CardProtocol.TangemException_ExtendedLengthNotSupported) { + if (!NoExtendedLengthSupportDialog.allreadyShowed) { + new NoExtendedLengthSupportDialog().show(getFragmentManager(), "NoExtendedLengthSupportDialog"); + } + } else { + Toast.makeText(getBaseContext(), "Try to scan again", Toast.LENGTH_LONG).show(); + } + progressBar.setProgress(100); + progressBar.setProgressTintList(ColorStateList.valueOf(Color.RED)); + } + }); + } + } + } + + progressBar.postDelayed(new Runnable() { + @Override + public void run() { + try { + progressBar.setProgress(0); + progressBar.setProgressTintList(ColorStateList.valueOf(Color.DKGRAY)); + progressBar.setVisibility(View.INVISIBLE); + } catch (Exception e) { + e.printStackTrace(); + } + } + }, 500); + } + + public void OnReadProgress(CardProtocol protocol, final int progress) { + progressBar.post(new Runnable() { + @Override + public void run() { + progressBar.setProgress(progress); + } + }); + } + + public void OnReadCancel() { + + purgeTask = null; + + progressBar.postDelayed(new Runnable() { + @Override + public void run() { + try { + progressBar.setProgress(0); + progressBar.setProgressTintList(ColorStateList.valueOf(Color.DKGRAY)); + progressBar.setVisibility(View.INVISIBLE); + } catch (Exception e) { + e.printStackTrace(); + } + } + }, 500); + } + + @Override + public void OnReadWait(final int msec) { + WaitSecurityDelayDialog.OnReadWait(this, msec); + } + + @Override + public void OnReadBeforeRequest(int timeout) { + WaitSecurityDelayDialog.onReadBeforeRequest(this, timeout); + } + + @Override + public void OnReadAfterRequest() { + WaitSecurityDelayDialog.onReadAfterRequest(this); + } + +} + diff --git a/app/src/main/java/com/tangem/wallet/QRScanActivity.java b/app/src/main/java/com/tangem/wallet/QRScanActivity.java new file mode 100644 index 0000000000..bd555763b4 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/QRScanActivity.java @@ -0,0 +1,90 @@ +package com.tangem.wallet; + +import android.Manifest; +import android.app.Activity; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.os.Bundle; +import android.support.v4.app.ActivityCompat; +import android.support.v7.app.AppCompatActivity; +import android.util.Log; + +import com.google.zxing.Result; + +import me.dm7.barcodescanner.zxing.ZXingScannerView; + +public class QRScanActivity extends AppCompatActivity implements ZXingScannerView.ResultHandler{ + + private ZXingScannerView mScannerView; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + //setContentView(R.layout.activity_qrscan); + if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) { + Log.e("QRScanActivity","User hasn't granted permission to use camera"); + ActivityCompat.requestPermissions(this, new String[] { Manifest.permission.CAMERA}, 1); + }else { + runScanner(); + } + } + + void runScanner() + { + mScannerView = new ZXingScannerView(this); // Programmatically initialize the scanner view + setContentView(mScannerView); + mScannerView.setResultHandler(this); // Register ourselves as a handler for scan results. + mScannerView.startCamera(); + } + + @Override + public void onRequestPermissionsResult(int requestCode, + String permissions[], int[] grantResults) { + switch (requestCode) { + case 1: { + // If request is cancelled, the result arrays are empty. + if (grantResults.length > 0 + && grantResults[0] == PackageManager.PERMISSION_GRANTED) { + + Log.i("QRScanActivity","permission was granted"); + // permission was granted, yay! Do the + // contacts-related task you need to do. + runScanner(); + + } else { + Log.e("QRScanActivity","permission denied"); + setResult(Activity.RESULT_CANCELED); + finish(); + + // permission denied, boo! Disable the + // functionality that depends on this permission. + } + return; + } + + // other 'case' lines to check for other + // permissions this app might request + } + } + + + @Override + public void handleResult(Result result) { + Intent data=new Intent(); + data.putExtra("QRCode", result.getText()); + setResult(Activity.RESULT_OK, data); + finish(); + } + + @Override + protected void onPause() { + super.onPause(); + if( mScannerView!=null ) mScannerView.stopCamera(); // Stop camera on pause + } + + @Override + protected void onResume() { + super.onResume(); + if( mScannerView!=null ) mScannerView.startCamera(); + } +} diff --git a/app/src/main/java/com/tangem/wallet/RLP.java b/app/src/main/java/com/tangem/wallet/RLP.java new file mode 100644 index 0000000000..b3d85d7687 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/RLP.java @@ -0,0 +1,228 @@ +package com.tangem.wallet; + +/** + * Created by Ilia on 07.01.2018. + */ + +import java.util.Arrays; + +import static com.tangem.wallet.ByteUtil.isNullOrZeroArray; +import static com.tangem.wallet.ByteUtil.isSingleZero; + +public class RLP { + public static final byte[] EMPTY_ELEMENT_RLP = encodeElement(new byte[0]); + + /** + * Allow for content up to size of 2^64 bytes * + */ + private static final double MAX_ITEM_LENGTH = Math.pow(256, 8); + + /** + * Reason for threshold according to Vitalik Buterin: + * - 56 bytes maximizes the benefit of both options + * - if we went with 60 then we would have only had 4 slots for long strings + * so RLP would not have been able to store objects above 4gb + * - if we went with 48 then RLP would be fine for 2^128 space, but that's way too much + * - so 56 and 2^64 space seems like the right place to put the cutoff + * - also, that's where Bitcoin's varint does the cutof + */ + private static final int SIZE_THRESHOLD = 56; + + /** RLP encoding rules are defined as follows: */ + + /* + * For a single byte whose value is in the [0x00, 0x7f] range, that byte is + * its own RLP encoding. + */ + + /** + * [0x80] + * If a string is 0-55 bytes long, the RLP encoding consists of a single + * byte with value 0x80 plus the length of the string followed by the + * string. The range of the first byte is thus [0x80, 0xb7]. + */ + private static final int OFFSET_SHORT_ITEM = 0x80; + + /** + * [0xb7] + * If a string is more than 55 bytes long, the RLP encoding consists of a + * single byte with value 0xb7 plus the length of the length of the string + * in binary form, followed by the length of the string, followed by the + * string. For example, a length-1024 string would be encoded as + * \xb9\x04\x00 followed by the string. The range of the first byte is thus + * [0xb8, 0xbf]. + */ + private static final int OFFSET_LONG_ITEM = 0xb7; + + /** + * [0xc0] + * If the total payload of a list (i.e. the combined length of all its + * items) is 0-55 bytes long, the RLP encoding consists of a single byte + * with value 0xc0 plus the length of the list followed by the concatenation + * of the RLP encodings of the items. The range of the first byte is thus + * [0xc0, 0xf7]. + */ + private static final int OFFSET_SHORT_LIST = 0xc0; + + public static byte[] encodeByte(byte singleByte) { + if ((singleByte & 0xFF) == 0) { + return new byte[]{(byte) OFFSET_SHORT_ITEM}; + } else if ((singleByte & 0xFF) <= 0x7F) { + return new byte[]{singleByte}; + } else { + return new byte[]{(byte) (OFFSET_SHORT_ITEM + 1), singleByte}; + } + } + + public static byte[] encodeShort(short singleShort) { + if ((singleShort & 0xFF) == singleShort) + return encodeByte((byte) singleShort); + else { + return new byte[]{(byte) (OFFSET_SHORT_ITEM + 2), + (byte) (singleShort >> 8 & 0xFF), + (byte) (singleShort >> 0 & 0xFF)}; + } + } + + public static byte[] encodeInt(int singleInt) { + if ((singleInt & 0xFF) == singleInt) + return encodeByte((byte) singleInt); + else if ((singleInt & 0xFFFF) == singleInt) + return encodeShort((short) singleInt); + else if ((singleInt & 0xFFFFFF) == singleInt) + return new byte[]{(byte) (OFFSET_SHORT_ITEM + 3), + (byte) (singleInt >>> 16), + (byte) (singleInt >>> 8), + (byte) singleInt}; + else { + return new byte[]{(byte) (OFFSET_SHORT_ITEM + 4), + (byte) (singleInt >>> 24), + (byte) (singleInt >>> 16), + (byte) (singleInt >>> 8), + (byte) singleInt}; + } + } + + private static final int OFFSET_LONG_LIST = 0xf7; + + public static byte[] encodeElement2(byte[] srcData) { + if (srcData == null) + return new byte[]{(byte) OFFSET_SHORT_ITEM}; + else if (srcData.length == 1 && (srcData[0] & 0xFF) < 0x80) { + return srcData; + } else if (srcData.length < SIZE_THRESHOLD) { + // length = 8X + byte length = (byte) (OFFSET_SHORT_ITEM + srcData.length); + byte[] data = Arrays.copyOf(srcData, srcData.length + 1); + System.arraycopy(data, 0, data, 1, srcData.length); + data[0] = length; + + return data; + } else { + // length of length = BX + // prefix = [BX, [length]] + int tmpLength = srcData.length; + byte byteNum = 0; + while (tmpLength != 0) { + ++byteNum; + tmpLength = tmpLength >> 8; + } + byte[] lenBytes = new byte[byteNum]; + for (int i = 0; i < byteNum; ++i) { + lenBytes[byteNum - 1 - i] = (byte) ((srcData.length >> (8 * i)) & 0xFF); + } + // first byte = F7 + bytes.length + byte[] data = Arrays.copyOf(srcData, srcData.length + 1 + byteNum); + System.arraycopy(data, 0, data, 1 + byteNum, srcData.length); + data[0] = (byte) (OFFSET_LONG_ITEM + byteNum); + System.arraycopy(lenBytes, 0, data, 1, lenBytes.length); + + return data; + } + } + public static byte[] encodeElement(byte[] srcData) { + + if (isNullOrZeroArray(srcData)) + return new byte[]{(byte) OFFSET_SHORT_ITEM}; + else if (isSingleZero(srcData)) + return srcData; + else if (srcData.length == 1 && (srcData[0] & 0xFF) < 0x80) { + return srcData; + } else if (srcData.length < SIZE_THRESHOLD) { + // length = 8X + byte length = (byte) (OFFSET_SHORT_ITEM + srcData.length); + byte[] data = Arrays.copyOf(srcData, srcData.length + 1); + System.arraycopy(data, 0, data, 1, srcData.length); + data[0] = length; + + return data; + } else { + // length of length = BX + // prefix = [BX, [length]] + int tmpLength = srcData.length; + byte byteNum = 0; + while (tmpLength != 0) { + ++byteNum; + tmpLength = tmpLength >> 8; + } + byte[] lenBytes = new byte[byteNum]; + for (int i = 0; i < byteNum; ++i) { + lenBytes[byteNum - 1 - i] = (byte) ((srcData.length >> (8 * i)) & 0xFF); + } + // first byte = F7 + bytes.length + byte[] data = Arrays.copyOf(srcData, srcData.length + 1 + byteNum); + System.arraycopy(data, 0, data, 1 + byteNum, srcData.length); + data[0] = (byte) (OFFSET_LONG_ITEM + byteNum); + System.arraycopy(lenBytes, 0, data, 1, lenBytes.length); + + return data; + } + } + + + public static byte[] encodeList(byte[]... elements) { + + if (elements == null) { + return new byte[]{(byte) OFFSET_SHORT_LIST}; + } + + int totalLength = 0; + for (byte[] element1 : elements) { + totalLength += element1.length; + } + + byte[] data; + int copyPos; + if (totalLength < SIZE_THRESHOLD) { + + data = new byte[1 + totalLength]; + data[0] = (byte) (OFFSET_SHORT_LIST + totalLength); + copyPos = 1; + } else { + // length of length = BX + // prefix = [BX, [length]] + int tmpLength = totalLength; + byte byteNum = 0; + while (tmpLength != 0) { + ++byteNum; + tmpLength = tmpLength >> 8; + } + tmpLength = totalLength; + byte[] lenBytes = new byte[byteNum]; + for (int i = 0; i < byteNum; ++i) { + lenBytes[byteNum - 1 - i] = (byte) ((tmpLength >> (8 * i)) & 0xFF); + } + // first byte = F7 + bytes.length + data = new byte[1 + lenBytes.length + totalLength]; + data[0] = (byte) (OFFSET_LONG_LIST + byteNum); + System.arraycopy(lenBytes, 0, data, 1, lenBytes.length); + + copyPos = lenBytes.length + 1; + } + for (byte[] element : elements) { + System.arraycopy(element, 0, data, copyPos, element.length); + copyPos += element.length; + } + return data; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/wallet/RLPElement.java b/app/src/main/java/com/tangem/wallet/RLPElement.java new file mode 100644 index 0000000000..ae82526c2d --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/RLPElement.java @@ -0,0 +1,12 @@ +package com.tangem.wallet; + +import java.io.Serializable; + +/** + * Created by Ilia on 07.01.2018. + */ + +public interface RLPElement extends Serializable { + + byte[] getRLPData(); +} diff --git a/app/src/main/java/com/tangem/wallet/RLPList.java b/app/src/main/java/com/tangem/wallet/RLPList.java new file mode 100644 index 0000000000..14164502b3 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/RLPList.java @@ -0,0 +1,37 @@ +package com.tangem.wallet; + +import java.util.ArrayList; + +/** + * Created by Ilia on 07.01.2018. + */ + +public class RLPList extends ArrayList implements RLPElement { + + byte[] rlpData; + + public void setRLPData(byte[] rlpData) { + this.rlpData = rlpData; + } + + public byte[] getRLPData() { + return rlpData; + } + + public static void recursivePrint(RLPElement element) { + + if (element == null) + throw new RuntimeException("RLPElement object can't be null"); + if (element instanceof RLPList) { + + RLPList rlpList = (RLPList) element; + System.out.print("["); + for (RLPElement singleElement : rlpList) + recursivePrint(singleElement); + System.out.print("]"); + } else { + String hex = BTCUtils.toHex(element.getRLPData()); + System.out.print(hex + ", "); + } + } +} diff --git a/app/src/main/java/com/tangem/wallet/RequestPINActivity.java b/app/src/main/java/com/tangem/wallet/RequestPINActivity.java new file mode 100644 index 0000000000..b35a4ec71e --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/RequestPINActivity.java @@ -0,0 +1,531 @@ +package com.tangem.wallet; + +import android.Manifest; +import android.annotation.SuppressLint; +import android.annotation.TargetApi; +import android.app.Activity; +import android.app.KeyguardManager; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.hardware.fingerprint.FingerprintManager; +import android.nfc.NfcAdapter; +import android.nfc.Tag; +import android.os.AsyncTask; +import android.os.Build; +import android.os.Bundle; +import android.security.keystore.KeyGenParameterSpec; +import android.security.keystore.KeyPermanentlyInvalidatedException; +import android.security.keystore.KeyProperties; +import android.support.v4.app.ActivityCompat; +import android.support.v7.app.AppCompatActivity; +import android.text.TextUtils; +import android.util.Log; +import android.view.View; +import android.view.View.OnClickListener; +import android.widget.Button; +import android.widget.ImageView; +import android.widget.TextView; + +import com.tangem.cardReader.NfcManager; + +import java.io.IOException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.cert.CertificateException; + +import javax.crypto.Cipher; +import javax.crypto.KeyGenerator; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.SecretKey; +import javax.crypto.spec.IvParameterSpec; + +public class RequestPINActivity extends AppCompatActivity implements NfcAdapter.ReaderCallback, FingerprintHelper.FingerprintHelperListener { + + public enum Mode {RequestPIN, RequestPIN2, RequestNewPIN, RequestNewPIN2, ConfirmNewPIN, ConfirmNewPIN2} + + Mode mode; + boolean allowFingerprint = false; + + private NfcManager mNfcManager; + + private TextView tvPIN; + + private StartFingerprintReaderTask mStartFingerprintReaderTask; + + public static final String KEY_ALIAS = "pinKey"; + public static final String KEYSTORE = "AndroidKeyStore"; + + private FingerprintManager fingerprintManager; + private FingerprintHelper fingerprintHelper; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_request_pin); + + MainActivity.commonInit(getApplicationContext()); + + mNfcManager = new NfcManager(this, this); + + tvPIN = findViewById(R.id.pin); + + OnClickListener onButtonNClick = new OnClickListener() { + @Override + public void onClick(View view) { + tvPIN.setText(tvPIN.getText() + (String) ((Button) view).getText()); + } + }; + + Button btn0 = findViewById(R.id.btn0); + btn0.setOnClickListener(onButtonNClick); + Button btn1 = findViewById(R.id.btn1); + btn1.setOnClickListener(onButtonNClick); + Button btn2 = findViewById(R.id.btn2); + btn2.setOnClickListener(onButtonNClick); + Button btn3 = findViewById(R.id.btn3); + btn3.setOnClickListener(onButtonNClick); + Button btn4 = findViewById(R.id.btn4); + btn4.setOnClickListener(onButtonNClick); + Button btn5 = findViewById(R.id.btn5); + btn5.setOnClickListener(onButtonNClick); + Button btn6 = findViewById(R.id.btn6); + btn6.setOnClickListener(onButtonNClick); + Button btn7 = findViewById(R.id.btn7); + btn7.setOnClickListener(onButtonNClick); + Button btn8 = findViewById(R.id.btn8); + btn8.setOnClickListener(onButtonNClick); + Button btn9 = findViewById(R.id.btn9); + btn9.setOnClickListener(onButtonNClick); + Button btnBS = findViewById(R.id.btnBackspace); + btnBS.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View view) { + String S = tvPIN.getText().toString(); + if (S.length() > 0) { + tvPIN.setText(S.substring(0, S.length() - 1)); + } + } + }); + + Button btnContinue = findViewById(R.id.btnContinue); + btnContinue.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View view) { + doContinue(); + } + }); + + mode = Mode.valueOf(getIntent().getStringExtra("mode")); + TextView tvPrompt = findViewById(R.id.pin_prompt); + if (mode == Mode.RequestNewPIN) { + if (PINStorage.haveEncryptedPIN()) { + allowFingerprint = true; + tvPrompt.setText("Enter new PIN or use fingerprint scanner"); + } else { + tvPrompt.setText("Enter new PIN"); + } + } else if (mode == Mode.ConfirmNewPIN) { + tvPrompt.setText("Confirm new PIN"); + } else if (mode == Mode.RequestPIN) { + if (PINStorage.haveEncryptedPIN()) { + allowFingerprint = true; + tvPrompt.setText("Enter PIN or use fingerprint scanner"); + } else { + tvPrompt.setText("Enter PIN"); + } + } else if (mode == Mode.RequestNewPIN2) { + if (PINStorage.haveEncryptedPIN2()) { + allowFingerprint = true; + tvPrompt.setText("Enter new PIN2 or use fingerprint scanner"); + } else { + tvPrompt.setText("Enter new PIN2"); + } + } else if (mode == Mode.ConfirmNewPIN2) { + tvPrompt.setText("Confirm new PIN2"); + } else if (mode == Mode.RequestPIN2) { + String UID = getIntent().getStringExtra("UID"); + Tangem_Card mCard = new Tangem_Card(UID); + mCard.LoadFromBundle(getIntent().getBundleExtra("Card")); + + if (mCard.PIN2 == Tangem_Card.PIN2_Mode.DefaultPIN2 || mCard.PIN2 == Tangem_Card.PIN2_Mode.Unchecked) { + // if we know PIN2 or not try default previously - use it + PINStorage.setPIN2(PINStorage.getDefaultPIN2()); + setResult(Activity.RESULT_OK); + finish(); + return; + } + + if (PINStorage.haveEncryptedPIN2()) { + allowFingerprint = true; + tvPrompt.setText("Enter PIN2 or use fingerprint scanner"); + } else { + tvPrompt.setText("Enter PIN2"); + } + } + ImageView imgFingerprint = findViewById(R.id.imgFingerprint); + if (!allowFingerprint) { + imgFingerprint.setVisibility(View.GONE); + } else { + imgFingerprint.setVisibility(View.VISIBLE); + } + + } + + @Override + protected void onPause() { + super.onPause(); + + if (fingerprintHelper != null) + fingerprintHelper.cancel(); + + if (mStartFingerprintReaderTask != null) { + mStartFingerprintReaderTask.cancel(true); + mStartFingerprintReaderTask = null; + } + + mNfcManager.onPause(); + } + + @Override + protected void onStop() { + super.onStop(); + if (fingerprintHelper != null) + fingerprintHelper.cancel(); + + if (mStartFingerprintReaderTask != null) { + + mStartFingerprintReaderTask.cancel(true); + mStartFingerprintReaderTask = null; + } + + mNfcManager.onStop(); + } + + @Override + protected void onResume() { + super.onResume(); + mNfcManager.onResume(); + if (allowFingerprint) { + startFingerprintReader(); + } + } + + private void doContinue() { + if (mStartFingerprintReaderTask != null) { + return; + } + + // Reset errors. + tvPIN.setError(null); + + // Store values at the time of the login attempt. + String pin = tvPIN.getText().toString(); + + boolean cancel = false; + View focusView = null; + + if (mode == Mode.ConfirmNewPIN) { + if (!pin.equals(getIntent().getStringExtra("newPIN"))) { + tvPIN.setError(getString(R.string.error_pin_confirmation_failed)); + focusView = tvPIN; + cancel = true; + } + } else if (mode == Mode.ConfirmNewPIN2) { + if (!pin.equals(getIntent().getStringExtra("newPIN2"))) { + tvPIN.setError(getString(R.string.error_pin_confirmation_failed)); + focusView = tvPIN; + cancel = true; + } + } else { + if (TextUtils.isEmpty(pin)) { + tvPIN.setError(getString(R.string.error_empty_pin)); + focusView = tvPIN; + cancel = true; + } + } + + if (cancel) { + focusView.requestFocus(); + } else { + if (mode == Mode.RequestNewPIN || mode == Mode.ConfirmNewPIN) { + Intent resultData = new Intent(); + resultData.putExtra("newPIN", pin); + if (mode == Mode.ConfirmNewPIN) { + resultData.putExtra("confirmPIN", pin); + } + setResult(Activity.RESULT_OK, resultData); + finish(); + } else if (mode == Mode.RequestNewPIN2 || mode == Mode.ConfirmNewPIN2) { + Intent resultData = new Intent(); + resultData.putExtra("newPIN2", pin); + if (mode == Mode.ConfirmNewPIN2) { + resultData.putExtra("confirmPIN2", pin); + } + setResult(Activity.RESULT_OK, resultData); + finish(); + } else if (mode == Mode.RequestPIN) { + PINStorage.setUserPIN(pin); + setResult(Activity.RESULT_OK); + finish(); + } else if (mode == Mode.RequestPIN2) { + PINStorage.setPIN2(pin); + setResult(Activity.RESULT_OK); + finish(); + } + } + + } + + @Override + public void authenticationFailed(String error) { + doLog(error); + } + + @TargetApi(Build.VERSION_CODES.M) + @Override + public void authenticationSucceeded(FingerprintManager.AuthenticationResult result) { + doLog("Authentication succeeded!"); + Cipher cipher = result.getCryptoObject().getCipher(); + + if (mode == Mode.RequestNewPIN || mode == Mode.ConfirmNewPIN) { + Intent resultData = new Intent(); + String pin = PINStorage.loadEncryptedPIN(cipher); + resultData.putExtra("newPIN", pin); + resultData.putExtra("confirmPIN", pin); + setResult(Activity.RESULT_OK, resultData); + finish(); + } else if (mode == Mode.RequestNewPIN2 || mode == Mode.ConfirmNewPIN2) { + Intent resultData = new Intent(); + String pin = PINStorage.loadEncryptedPIN2(cipher); + resultData.putExtra("newPIN2", pin); + resultData.putExtra("confirmPIN2", pin); + setResult(Activity.RESULT_OK, resultData); + finish(); + } else if (mode == Mode.RequestPIN) { + PINStorage.loadEncryptedPIN(cipher); + setResult(Activity.RESULT_OK); + } else if (mode == Mode.RequestPIN2) { + PINStorage.loadEncryptedPIN2(cipher); + setResult(Activity.RESULT_OK); + } + + finish(); + } + + private void startFingerprintReader() { + if (!testFingerPrintSettings()) + return; + + if (!allowFingerprint) + return; + + fingerprintHelper = new FingerprintHelper(RequestPINActivity.this); + mStartFingerprintReaderTask = new StartFingerprintReaderTask(this, fingerprintManager, fingerprintHelper); + mStartFingerprintReaderTask.execute((Void) null); + } + + @Override + public void onTagDiscovered(Tag tag) { + try { + Log.w(getClass().getName(), "Ignore discovered tag!"); + mNfcManager.IgnoreTag(tag); + } catch (IOException e) { + e.printStackTrace(); + } + } + + + public static class StartFingerprintReaderTask extends AsyncTask { + private KeyStore keyStore; + private Cipher cipher; + + private FingerprintManager.CryptoObject cryptoObject; + + FingerprintManager fingerprintManager; + FingerprintHelper fingerprintHelper; + + RequestPINActivity activity; + + StartFingerprintReaderTask(RequestPINActivity activity, FingerprintManager fingerprintManager, FingerprintHelper fingerprintHelper) { + this.fingerprintManager = fingerprintManager; + this.fingerprintHelper = fingerprintHelper; + this.activity = activity; + } + + @Override + protected Boolean doInBackground(Void... params) { + if (!getKeyStore()) + return false; + + if (!createNewKey(false)) + return false; + + if (!getCipher()) + return false; + + if (!initCipher(Cipher.DECRYPT_MODE)) + return false; + + return initCryptObject(); + } + + @Override + protected void onPostExecute(final Boolean success) { + onCancelled(); + + if (!success) { + doLog("Authentication failed!"); + } else { + fingerprintHelper.startAuth(fingerprintManager, cryptoObject); + doLog("Authenticate using fingerprint!"); + + } + } + + @Override + protected void onCancelled() { + activity.mStartFingerprintReaderTask = null; + } + + private boolean getKeyStore() { + doLog("Getting keystore..."); + try { + keyStore = KeyStore.getInstance(KEYSTORE); + keyStore.load(null); // Create empty keystore + return true; + } catch (KeyStoreException | CertificateException | NoSuchAlgorithmException | IOException e) { + e.printStackTrace(); + } + + return false; + } + + @TargetApi(Build.VERSION_CODES.M) + public boolean createNewKey(boolean forceCreate) { + doLog("Creating new key..."); + try { + if (forceCreate) + keyStore.deleteEntry(KEY_ALIAS); + + if (!keyStore.containsAlias(KEY_ALIAS)) { + KeyGenerator generator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, KEYSTORE); + + generator.init(new KeyGenParameterSpec.Builder(KEY_ALIAS, + KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT) + .setBlockModes(KeyProperties.BLOCK_MODE_CBC) + .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7) + .setUserAuthenticationRequired(true) + .build() + ); + + generator.generateKey(); + doLog("Key created."); + } else + doLog("Key exists."); + + return true; + } catch (Exception e) { + e.printStackTrace(); + } + + return false; + } + + private boolean getCipher() { + doLog("Getting cipher..."); + try { + cipher = Cipher.getInstance( + KeyProperties.KEY_ALGORITHM_AES + "/" + + KeyProperties.BLOCK_MODE_CBC + "/" + + KeyProperties.ENCRYPTION_PADDING_PKCS7); + + return true; + } catch (NoSuchAlgorithmException | NoSuchPaddingException e) { + e.printStackTrace(); + } + + return false; + } + + @TargetApi(Build.VERSION_CODES.M) + private boolean initCipher(int mode) { + doLog("Initializing cipher..."); + try { + keyStore.load(null); + SecretKey keyspec = (SecretKey) keyStore.getKey(KEY_ALIAS, null); + + if (mode == Cipher.ENCRYPT_MODE) { + cipher.init(mode, keyspec); + } else { + byte[] iv = null; + if (activity.mode == Mode.RequestPIN || activity.mode == Mode.RequestNewPIN || activity.mode == Mode.ConfirmNewPIN) { + iv = PINStorage.loadEncryptedIV(); + } else if (activity.mode == Mode.RequestPIN2 || activity.mode == Mode.RequestNewPIN2 || activity.mode == Mode.ConfirmNewPIN2) { + iv = PINStorage.loadEncryptedIV2(); + } + IvParameterSpec ivspec = new IvParameterSpec(iv); + cipher.init(mode, keyspec, ivspec); + } + + return true; + } catch (KeyPermanentlyInvalidatedException e) { + e.printStackTrace(); + createNewKey(true); // Retry after clearing entry + } catch (Exception e) { + e.printStackTrace(); + } + + return false; + } + + @TargetApi(Build.VERSION_CODES.M) + private boolean initCryptObject() { + doLog("Initializing crypt object..."); + try { + cryptoObject = new FingerprintManager.CryptoObject(cipher); + return true; + } catch (Exception e) { + e.printStackTrace(); + } + return false; + } + + + } + + public static void doLog(String text) { +// Log.e("FP", text); + } + + @SuppressLint("NewApi") + private boolean testFingerPrintSettings() { + doLog("Testing Fingerprint Settings"); + + KeyguardManager keyguardManager = (KeyguardManager) getSystemService(KEYGUARD_SERVICE); + fingerprintManager = (FingerprintManager) getSystemService(FINGERPRINT_SERVICE); + + assert keyguardManager != null; + if (!keyguardManager.isKeyguardSecure()) { + doLog("User hasn't enabled Lock Screen"); + return false; + } + + if (ActivityCompat.checkSelfPermission(this, Manifest.permission.USE_FINGERPRINT) != PackageManager.PERMISSION_GRANTED) { + doLog("User hasn't granted permission to use Fingerprint"); + return false; + } + + if (!fingerprintManager.hasEnrolledFingerprints()) { + doLog("User hasn't registered any fingerprints"); + return false; + } + + doLog("Fingerprint authentication is set.\n"); + + return true; + } + + +} + diff --git a/app/src/main/java/com/tangem/wallet/SavePINActivity.java b/app/src/main/java/com/tangem/wallet/SavePINActivity.java new file mode 100644 index 0000000000..02f519364b --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/SavePINActivity.java @@ -0,0 +1,543 @@ +package com.tangem.wallet; + +import android.Manifest; +import android.annotation.SuppressLint; +import android.annotation.TargetApi; +import android.app.Dialog; +import android.app.KeyguardManager; +import android.content.pm.PackageManager; +import android.hardware.fingerprint.FingerprintManager; +import android.os.AsyncTask; +import android.os.Build; +import android.os.Bundle; +import android.security.keystore.KeyGenParameterSpec; +import android.security.keystore.KeyPermanentlyInvalidatedException; +import android.security.keystore.KeyProperties; +import android.support.v4.app.ActivityCompat; +import android.support.v7.app.AppCompatActivity; +import android.text.TextUtils; +import android.view.View; +import android.view.View.OnClickListener; +import android.widget.Button; +import android.widget.CheckBox; +import android.widget.TextView; +import android.widget.Toast; + +import java.io.IOException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.cert.CertificateException; + +import javax.crypto.Cipher; +import javax.crypto.KeyGenerator; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.SecretKey; +import javax.crypto.spec.IvParameterSpec; + +//import com.afollestad.materialdialogs.AlertDialogWrapper; + +public class SavePINActivity extends AppCompatActivity implements FingerprintHelper.FingerprintHelperListener { + + private TextView tvPIN; + private CheckBox chkUseFingerprint; + + private ConfirmWithFingerprintTask mConfirmWithFingerprintTask; + + + private KeyStore keyStore; + private Cipher cipher; + private FingerprintManager fingerprintManager; + private FingerprintManager.CryptoObject cryptoObject; + private FingerprintHelper fingerprintHelper; + + private boolean UsePIN2=false; + + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_save_pin); + + MainActivity.commonInit(getApplicationContext()); + + UsePIN2=getIntent().getBooleanExtra("PIN2", false); + + tvPIN = findViewById(R.id.pin); + OnClickListener onButtonNClick = new OnClickListener() { + @Override + public void onClick(View view) { + tvPIN.setText(String.format("%s%s", tvPIN.getText(),((Button) view).getText())); + } + }; + + if( UsePIN2 ) { + ((TextView) findViewById(R.id.pin_prompt)).setText(R.string.enter_pin2_and_use_fingerprint_to_save_it); + }else{ + ((TextView) findViewById(R.id.pin_prompt)).setText(R.string.enter_pin_and_use_fingerprint_to_save_it); + } + + chkUseFingerprint = (CheckBox) findViewById(R.id.chkUseFingerprint); + + if( UsePIN2 ) { + chkUseFingerprint.setChecked(true); + chkUseFingerprint.setEnabled(false); + }else{ + chkUseFingerprint.setChecked(PINStorage.haveEncryptedPIN()); + chkUseFingerprint.setEnabled(true); + } + + + Button btn0 = findViewById(R.id.btn0); + btn0.setOnClickListener(onButtonNClick); + Button btn1 = findViewById(R.id.btn1); + btn1.setOnClickListener(onButtonNClick); + Button btn2 = findViewById(R.id.btn2); + btn2.setOnClickListener(onButtonNClick); + Button btn3 = findViewById(R.id.btn3); + btn3.setOnClickListener(onButtonNClick); + Button btn4 = findViewById(R.id.btn4); + btn4.setOnClickListener(onButtonNClick); + Button btn5 = findViewById(R.id.btn5); + btn5.setOnClickListener(onButtonNClick); + Button btn6 = findViewById(R.id.btn6); + btn6.setOnClickListener(onButtonNClick); + Button btn7 = findViewById(R.id.btn7); + btn7.setOnClickListener(onButtonNClick); + Button btn8 = findViewById(R.id.btn8); + btn8.setOnClickListener(onButtonNClick); + Button btn9 = findViewById(R.id.btn9); + btn9.setOnClickListener(onButtonNClick); + Button btnBS = findViewById(R.id.btnBackspace); + btnBS.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View view) { + String S = tvPIN.getText().toString(); + if (S.length() > 0) { + tvPIN.setText(S.substring(0, S.length() - 1)); + } + } + }); + + Button btnSave = findViewById(R.id.btnSavePIN); + btnSave.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View view) { + doSavePIN(); + } + }); + + Button btnDelete = findViewById(R.id.btnDeletePIN); + btnDelete.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View view) { + doDeletePIN(); + } + }); + + } + + @Override + protected void onPause() { + super.onPause(); + + if (fingerprintHelper != null) + fingerprintHelper.cancel(); + + if (mConfirmWithFingerprintTask != null) + mConfirmWithFingerprintTask.cancel(true); + } + + @Override + protected void onStop() { + super.onStop(); + if (fingerprintHelper != null) + fingerprintHelper.cancel(); + + if (mConfirmWithFingerprintTask != null) + mConfirmWithFingerprintTask.cancel(true); + } + + + private enum OnConfirmAction {Save, DeleteEncryptedAndSave, Delete} + OnConfirmAction onConfirmAction; + + private void doSavePIN() { + if (mConfirmWithFingerprintTask != null) { + return; + } + + tvPIN.setError(null); + + String pin = tvPIN.getText().toString(); + + boolean cancel = false; + View focusView = null; + + if (TextUtils.isEmpty(pin)) { + tvPIN.setError(getString(R.string.error_empty_pin)); + focusView = tvPIN; + cancel = true; + } + + if (cancel) { + focusView.requestFocus(); + } else { + + if( UsePIN2 ) + { + if (!testFingerPrintSettings()) { + tvPIN.postDelayed(new Runnable() { + @Override + public void run() { + finish(); + } + }, 2000); + return; + } + + onConfirmAction = OnConfirmAction.Save; + + mConfirmWithFingerprintTask = new ConfirmWithFingerprintTask(); + mConfirmWithFingerprintTask.execute((Void) null); + }else { + if (chkUseFingerprint.isChecked() || PINStorage.haveEncryptedPIN()) { + if (!testFingerPrintSettings()) { + tvPIN.postDelayed(new Runnable() { + @Override + public void run() { + finish(); + } + }, 2000); + return; + } + + if (chkUseFingerprint.isChecked()) { + onConfirmAction = OnConfirmAction.Save; + } else { + onConfirmAction = OnConfirmAction.DeleteEncryptedAndSave; + } + + // Show a progress spinner, and kick off a background task to + // perform the user login attempt. + //showProgress(true); + mConfirmWithFingerprintTask = new ConfirmWithFingerprintTask(); + mConfirmWithFingerprintTask.execute((Void) null); + } else { + PINStorage.savePIN(tvPIN.getText().toString()); + finish(); + } + } + } + } + + private void doDeletePIN() { + if( UsePIN2 ) + { + if (PINStorage.haveEncryptedPIN2()) { + if (!testFingerPrintSettings()) { + tvPIN.postDelayed(new Runnable() { + @Override + public void run() { + finish(); + } + }, 2000); + return; + } + onConfirmAction = OnConfirmAction.Delete; + mConfirmWithFingerprintTask = new ConfirmWithFingerprintTask(); + mConfirmWithFingerprintTask.execute((Void) null); + } else { + tvPIN.setText(""); + finish(); + } + }else { + if (chkUseFingerprint.isChecked() || PINStorage.haveEncryptedPIN()) { + if (!testFingerPrintSettings()) { + tvPIN.postDelayed(new Runnable() { + @Override + public void run() { + finish(); + } + }, 2000); + return; + } + onConfirmAction = OnConfirmAction.Delete; + mConfirmWithFingerprintTask = new ConfirmWithFingerprintTask(); + mConfirmWithFingerprintTask.execute((Void) null); + } else { + tvPIN.setText(""); + PINStorage.deletePIN(); + finish(); + } + } + } + + + @Override + public void authenticationFailed(String error) { + print(error); + } + + @TargetApi(Build.VERSION_CODES.M) + @Override + public void authenticationSucceeded(FingerprintManager.AuthenticationResult result) { + print("Authentication succeeded!"); + cipher = result.getCryptoObject().getCipher(); + + switch (onConfirmAction) { + case Save: + String textToEncrypt = tvPIN.getText().toString(); + if( UsePIN2 ) + { + PINStorage.saveEncryptedPIN2(cipher, textToEncrypt); + }else { + PINStorage.saveEncryptedPIN(cipher, textToEncrypt); + } + print(R.string.pin_save_success); + break; + case Delete: + if( UsePIN2 ) + { + PINStorage.deleteEncryptedPIN2(); + }else { + PINStorage.deleteEncryptedPIN(); + PINStorage.deletePIN(); + } + tvPIN.setText(""); + break; + case DeleteEncryptedAndSave: + if( UsePIN2 ) + { + PINStorage.deleteEncryptedPIN2(); + PINStorage.saveEncryptedPIN2(cipher, tvPIN.getText().toString()); + }else { + PINStorage.deleteEncryptedPIN(); + PINStorage.savePIN(tvPIN.getText().toString()); + } + break; + } + dFingerPrintConfirmation.dismiss(); + finish(); + } + + private class ConfirmWithFingerprintTask extends AsyncTask { + ConfirmWithFingerprintTask() { + fingerprintHelper = new FingerprintHelper(SavePINActivity.this); + } + + @Override + protected Boolean doInBackground(Void... params) { + if (!getKeyStore()) + return false; + + if (!createNewKey(false)) + return false; + + if (!getCipher()) + return false; + + return initCipher(Cipher.ENCRYPT_MODE) && initCryptObject(); + + } + + @Override + protected void onPostExecute(final Boolean success) { + onCancelled(); + + if (!success) { + Toast.makeText(getBaseContext(), R.string.pin_save_fail, Toast.LENGTH_LONG).show(); + } else { + print("Confirm PIN action using fingerprint!"); + fingerprintHelper.startAuth(fingerprintManager, cryptoObject); + CreateFingerPrintConfirmationDialog(); + } + } + + @Override + protected void onCancelled() { + mConfirmWithFingerprintTask = null; + if (dFingerPrintConfirmation != null) { + dFingerPrintConfirmation.cancel(); + } + } + } + + public void print(String text) { +// Log.e("FP", text); + } + + public void print(int id) { + print(getString(id)); + } + + @SuppressLint("NewApi") + private boolean testFingerPrintSettings() { + print("Testing Fingerprint Settings"); + + KeyguardManager keyguardManager = (KeyguardManager) getSystemService(KEYGUARD_SERVICE); + fingerprintManager = (FingerprintManager) getSystemService(FINGERPRINT_SERVICE); + + if (!keyguardManager.isKeyguardSecure()) { + print("User hasn't enabled Lock Screen"); + Toast.makeText(getBaseContext(), "User hasn't enabled Lock Screen", Toast.LENGTH_LONG).show(); + return false; + } + + if (ActivityCompat.checkSelfPermission(this, Manifest.permission.USE_FINGERPRINT) != PackageManager.PERMISSION_GRANTED) { + print("User hasn't granted permission to use Fingerprint"); + Toast.makeText(getBaseContext(), "User hasn't granted permission to use Fingerprint", Toast.LENGTH_LONG).show(); + return false; + } + + if (!fingerprintManager.hasEnrolledFingerprints()) { + print("User hasn't registered any fingerprints"); + Toast.makeText(getBaseContext(), "User hasn't registered any fingerprints", Toast.LENGTH_LONG).show(); + return false; + } + + print("Fingerprint authentication is set.\n"); + + return true; + } + + private boolean getKeyStore() { + print("Getting keystore..."); + try { + keyStore = KeyStore.getInstance(RequestPINActivity.KEYSTORE); + keyStore.load(null); // Create empty keystore + return true; + } catch (KeyStoreException | CertificateException | NoSuchAlgorithmException | IOException e) { + e.printStackTrace(); + } + + return false; + } + + @TargetApi(Build.VERSION_CODES.M) + public boolean createNewKey(boolean forceCreate) { + print("Creating new key..."); + try { + if (forceCreate) + keyStore.deleteEntry(RequestPINActivity.KEY_ALIAS); + + if (!keyStore.containsAlias(RequestPINActivity.KEY_ALIAS)) { + KeyGenerator generator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, RequestPINActivity.KEYSTORE); + + generator.init(new KeyGenParameterSpec.Builder(RequestPINActivity.KEY_ALIAS, + KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT) + .setBlockModes(KeyProperties.BLOCK_MODE_CBC) + .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7) + .setUserAuthenticationRequired(true) + .build() + ); + + generator.generateKey(); + print("Key created."); + } else + print("Key exists."); + + return true; + } catch (Exception e) { + print(e.getMessage()); + } + + return false; + } + + private boolean getCipher() { + print("Getting cipher..."); + try { + cipher = Cipher.getInstance( + KeyProperties.KEY_ALGORITHM_AES + "/" + + KeyProperties.BLOCK_MODE_CBC + "/" + + KeyProperties.ENCRYPTION_PADDING_PKCS7); + + return true; + } catch (NoSuchAlgorithmException | NoSuchPaddingException e) { + e.printStackTrace(); + } + + return false; + } + + @TargetApi(Build.VERSION_CODES.M) + private boolean initCipher(int mode) { + print("Initializing cipher..."); + try { + keyStore.load(null); + SecretKey keyspec = (SecretKey) keyStore.getKey(RequestPINActivity.KEY_ALIAS, null); + + if (mode == Cipher.ENCRYPT_MODE) { + cipher.init(mode, keyspec); + } else { + byte[] iv = PINStorage.loadEncryptedIV(); + IvParameterSpec ivspec = new IvParameterSpec(iv); + cipher.init(mode, keyspec, ivspec); + } + + return true; + } catch (KeyPermanentlyInvalidatedException e) { + e.printStackTrace(); + createNewKey(true); // Retry after clearing entry + } catch (Exception e) { + e.printStackTrace(); + } + + return false; + } + + @TargetApi(Build.VERSION_CODES.M) + private boolean initCryptObject() { + print("Initializing crypt object..."); + try { + cryptoObject = new FingerprintManager.CryptoObject(cipher); + return true; + } catch (Exception e) { + e.printStackTrace(); + } + return false; + } + + Dialog dFingerPrintConfirmation = null; + + private void CreateFingerPrintConfirmationDialog() { +// final AlertDialogWrapper.Builder b = new AlertDialogWrapper.Builder(this); +// switch (onConfirmAction) +// { +// case Save: +// if( UsePIN2) { +// b.setTitle("Confirm new PIN2 saving "); +// }else{ +// b.setTitle("Confirm new PIN saving "); +// } +// break; +// case Delete: +// if( UsePIN2 ) { +// b.setTitle("Confirm PIN2 deleting"); +// }else{ +// b.setTitle("Confirm PIN deleting"); +// } +// break; +// case DeleteEncryptedAndSave: +// if( UsePIN2 ) { +// b.setTitle("Confirm deleting old saved PIN2"); +// }else { +// b.setTitle("Confirm deleting old saved PIN"); +// } +// break; +// } +// View view = getLayoutInflater().inflate(R.layout.dialog_fingerprint_confirmation, null); +// b.setView(view); + +// dFingerPrintConfirmation = b.show(); +// dFingerPrintConfirmation.setOnCancelListener(new DialogInterface.OnCancelListener() { +// @Override +// public void onCancel(DialogInterface dialog) { +// fingerprintHelper.cancel(); +// print("Cancel fingerprint confirmation"); +// } +// }); + } +} + diff --git a/app/src/main/java/com/tangem/wallet/SelectBlockchainActivity.java b/app/src/main/java/com/tangem/wallet/SelectBlockchainActivity.java new file mode 100644 index 0000000000..635daf6791 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/SelectBlockchainActivity.java @@ -0,0 +1,31 @@ +package com.tangem.wallet; + +import android.os.Bundle; +import android.support.v7.app.AppCompatActivity; + +public class SelectBlockchainActivity extends AppCompatActivity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_select_blockchain); + +// Spinner spBlockchain = (Spinner) findViewById(R.id.spBlockchain); +// ArrayAdapter adapter = new ArrayAdapter(this, android.R.layout.simple_spinner_item, Blockchain.values()); +// spBlockchain.setAdapter(adapter); +// spBlockchain.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { +// @Override +// public void onItemSelected(AdapterView parent, View view, int position, long id) { +// Intent intent = new Intent(); +// intent.putExtra("blockchain", Blockchain.values()[position].toString()); +// setResult(RESULT_OK, intent); +// finish(); +// } +// +// @Override +// public void onNothingSelected(AdapterView parent) { +// +// } +// }); + } +} diff --git a/app/src/main/java/com/tangem/wallet/SendTransactionActivity.java b/app/src/main/java/com/tangem/wallet/SendTransactionActivity.java new file mode 100644 index 0000000000..84c64e2f88 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/SendTransactionActivity.java @@ -0,0 +1,208 @@ +package com.tangem.wallet; + +import android.content.Intent; +import android.os.AsyncTask; +import android.support.v7.app.AppCompatActivity; +import android.os.Bundle; +import android.util.Log; +import android.view.KeyEvent; +import android.widget.ProgressBar; +import android.widget.Toast; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.math.BigInteger; +import java.util.List; + +public class SendTransactionActivity extends AppCompatActivity { + + ProgressBar progressBar; + private Tangem_Card mCard; + private String tx; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_send_transaction); + + MainActivity.commonInit(getApplicationContext()); + + progressBar = findViewById(R.id.progressBar); + + Intent intent = getIntent(); + mCard = new Tangem_Card(getIntent().getStringExtra("UID")); + mCard.LoadFromBundle(intent.getExtras().getBundle("Card")); + tx = intent.getStringExtra("TX"); + + CoinEngine engine = CoinEngineFactory.Create(mCard.getBlockchain()); + if (mCard.getBlockchain() == Blockchain.Ethereum || mCard.getBlockchain() == Blockchain.EthereumTestNet || mCard.getBlockchain() == Blockchain.Token) { + ETHRequestTask task = new ETHRequestTask(mCard.getBlockchain()); + Infura_Request req = Infura_Request.SendTransaction(mCard.getWallet(), tx); + req.setID(67); + req.setBlockchain(mCard.getBlockchain()); + task.execute(req); + } else if (mCard.getBlockchain() == Blockchain.Bitcoin || mCard.getBlockchain() == Blockchain.BitcoinTestNet ) { + String nodeAddress = engine.GetNode(mCard); + int nodePort = engine.GetNodePort(mCard); + ConnectTask connectTask = new ConnectTask(nodeAddress, nodePort); + connectTask.execute(Electrum_Request.Broadcast(mCard.getWallet(), tx)); + } + else if (mCard.getBlockchain() == Blockchain.BitcoinCash || mCard.getBlockchain() == Blockchain.BitcoinCashTestNet ) { + String nodeAddress = engine.GetNode(mCard); + int nodePort = engine.GetNodePort(mCard); + ConnectTask connectTask = new ConnectTask(nodeAddress, nodePort); + connectTask.execute(Electrum_Request.Broadcast(mCard.getWallet(), tx)); + } + + } + + @Override + public boolean onKeyDown(int keycode, KeyEvent e) { + switch (keycode) { + case KeyEvent.KEYCODE_BACK: + Toast.makeText(getBaseContext(),"Please wait while the payment is sent...",Toast.LENGTH_LONG).show(); + return true; + } + + return super.onKeyDown(keycode, e); + } + + void FinishWithError(String Message) { + Intent intent = new Intent(); + intent.putExtra("message", "Failed to send transaction. Try again."); + setResult(MainActivity.RESULT_CANCELED, intent); + finish(); + } + + void FinishWithSuccess() { + Intent intent = new Intent(); + intent.putExtra("message", "Transaction has been successfully signed and sent to blockchain node. Wallet balance will be updated in a while"); + setResult(MainActivity.RESULT_OK, intent); + finish(); + } + + private class ETHRequestTask extends Infura_Task { + ETHRequestTask(Blockchain blockchain){ + super(blockchain); + } + @Override + protected void onPostExecute(List requests) { + super.onPostExecute(requests); + for (Infura_Request request : requests) { + try { + if (request.error == null) { + if (request.isMethod(Infura_Request.METHOD_ETH_SendRawTransaction)) { + try { + String hashTX = ""; + try { + String tmp = request.getResultString(); + hashTX = tmp; + }catch(JSONException e) + { + JSONObject msg = request.getAnswer(); + JSONObject err = msg.getJSONObject("error"); + hashTX = err.getString("message"); + LastSignStorage.setLastMessage(mCard.getWallet(), hashTX); + FinishWithError(hashTX); + return; + } + + try { + if (hashTX.startsWith("0x") || hashTX.startsWith("0X")) { + hashTX = hashTX.substring(2); + } + BigInteger bigInt = new BigInteger(hashTX, 16); //TODO: очень плохой способ + LastSignStorage.setTxWasSend(mCard.getWallet()); + LastSignStorage.setLastMessage(mCard.getWallet(), ""); + BigInteger nonce = mCard.GetConfirmTXCount(); + nonce.add(BigInteger.valueOf(1)); + mCard.SetConfirmTXCount(nonce); + Log.e("TX_RESULT", hashTX); + FinishWithSuccess(); + }catch(Exception e) + { + FinishWithError(hashTX); + } + + } catch (JSONException e) { + e.printStackTrace(); + FinishWithError(e.toString()); + } + } + } else if (request.error != null) { + FinishWithError(request.error); + } + } catch (JSONException e) { + e.printStackTrace(); + FinishWithError(e.toString()); + } + } + } + } + + private class ConnectTask extends Electrum_Task { + public ConnectTask(String host, int port) { + super(host, port); + } + + public ConnectTask(String host, int port, SharedData sharedData) { + super(host, port, sharedData); + } + + @Override + protected void onProgressUpdate(Integer... values) { + super.onProgressUpdate(values); + } + + @Override + protected void onPostExecute(List requests) { + super.onPostExecute(requests); + CoinEngine engine = CoinEngineFactory.Create(Blockchain.Bitcoin); + + for (Electrum_Request request : requests) { + try { + if (request.error == null) { + if (request.isMethod(Electrum_Request.METHOD_SendTransaction)) { + try { + String hashTX = request.getResultString(); + + try + { + LastSignStorage.setLastMessage(mCard.getWallet(), hashTX); + if (hashTX.startsWith("0x") || hashTX.startsWith("0X")) { + hashTX = hashTX.substring(2); + } + BigInteger bigInt = new BigInteger(hashTX, 16); //TODO: очень плохой способ + LastSignStorage.setTxWasSend(mCard.getWallet()); + LastSignStorage.setLastMessage(mCard.getWallet(), ""); + Log.e("TX_RESULT", hashTX); + FinishWithSuccess(); + }catch(Exception e) + { + engine.SwitchNode(null); + FinishWithError(hashTX); + return; + } + + } catch (JSONException e) { + e.printStackTrace(); + engine.SwitchNode(null); + FinishWithError(e.toString()); + } + } + } else if (request.error != null) { + engine.SwitchNode(null); + FinishWithError(request.error); + } + } catch (JSONException e) { + e.printStackTrace(); + engine.SwitchNode(null); + FinishWithError(e.toString()); + } + } + + } + } + +} diff --git a/app/src/main/java/com/tangem/wallet/SharedData.java b/app/src/main/java/com/tangem/wallet/SharedData.java new file mode 100644 index 0000000000..45fcfa5def --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/SharedData.java @@ -0,0 +1,22 @@ +package com.tangem.wallet; + +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Created by Ilia on 12.04.2018. + */ + + +public class SharedData +{ + public static int COUNT_REQUEST = 3; + public AtomicInteger requestCounter; + public int allRequest; + public AtomicInteger errorRequest; + public SharedData(int requstCount) + { + allRequest = requstCount; + errorRequest = new AtomicInteger(0); + requestCounter = new AtomicInteger(0); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/wallet/SignPaymentActivity.java b/app/src/main/java/com/tangem/wallet/SignPaymentActivity.java new file mode 100644 index 0000000000..1f5c582659 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/SignPaymentActivity.java @@ -0,0 +1 @@ +package com.tangem.wallet; import android.app.Activity; import android.content.Intent; import android.content.res.ColorStateList; import android.graphics.Color; import android.nfc.NfcAdapter; import android.nfc.Tag; import android.nfc.tech.IsoDep; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.util.Log; import android.view.KeyEvent; import android.view.View; import android.widget.ProgressBar; import android.widget.TextView; import android.widget.Toast; import com.tangem.cardReader.CardProtocol; import com.tangem.cardReader.NfcManager; import com.tangem.cardReader.Util; public class SignPaymentActivity extends AppCompatActivity implements NfcAdapter.ReaderCallback, CardProtocol.Notifications { private static final int REQUEST_CODE_SEND_PAYMENT = 1; public static final int RESULT_INVALID_PIN = Activity.RESULT_FIRST_USER; private Tangem_Card mCard; private TextView tvCardID; private NfcManager mNfcManager; private static final String logTag = "SignPayment"; private ProgressBar progressBar; private SignPaymentTask signPaymentTask; private String amountStr; private String feeStr; private String outAddressStr; private boolean lastReadSuccess = true; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_sign_payment); MainActivity.commonInit(getApplicationContext()); mCard = new Tangem_Card(getIntent().getStringExtra("UID")); mCard.LoadFromBundle(getIntent().getExtras().getBundle("Card")); amountStr = getIntent().getStringExtra("Amount"); feeStr = getIntent().getStringExtra("Fee"); outAddressStr = getIntent().getStringExtra("Wallet"); tvCardID = findViewById(R.id.tvCardID); tvCardID.setText(mCard.getCIDDescription()); mNfcManager = new NfcManager(this, this); progressBar = findViewById(R.id.progressBar); progressBar.setProgressTintList(ColorStateList.valueOf(Color.DKGRAY)); progressBar.setVisibility(View.INVISIBLE); } @Override public void onTagDiscovered(Tag tag) { try { // get IsoDep handle and run cardReader thread final IsoDep isoDep = IsoDep.get(tag); if (isoDep == null) { throw new CardProtocol.TangemException(getString(R.string.wrong_tag_err)); } byte UID[] = tag.getId(); String sUID = Util.byteArrayToHexString(UID); Log.v(logTag, "UID: " + sUID); if (sUID.equals(mCard.getUID())) { if (lastReadSuccess) { isoDep.setTimeout(mCard.getPauseBeforePIN2() + 5000); } else { isoDep.setTimeout(mCard.getPauseBeforePIN2() + 65000); } signPaymentTask = new SignPaymentTask(isoDep, this, amountStr, feeStr, outAddressStr); signPaymentTask.start(); } else { Log.d(logTag, "Mismatch card UID (" + sUID + " instead of " + mCard.getUID() + ")"); mNfcManager.IgnoreTag(isoDep.getTag()); return; } } catch (Exception e) { e.printStackTrace(); } } @Override public void onResume() { super.onResume(); mNfcManager.onResume(); } @Override public void onPause() { mNfcManager.onPause(); if (signPaymentTask != null) { signPaymentTask.cancel(true); } super.onPause(); } @Override public void onStop() { // dismiss enable NFC dialog mNfcManager.onStop(); if (signPaymentTask != null) { signPaymentTask.cancel(true); } super.onStop(); } // @Override // public Dialog CreateNFCDialog(int id, AlertDialogWrapper.Builder builder, LayoutInflater li) { // return mNfcManager.onCreateDialog(id, builder, li); // } private class SignPaymentTask extends Thread { String txAmount = ""; String txFee = ""; public void SetTransactionValue(String amount, String fee) { txAmount = amount; txFee = fee; } private String txOutAddress; IsoDep mIsoDep; CardProtocol.Notifications mNotifications; private boolean isCancelled = false; public SignPaymentTask(IsoDep isoDep, CardProtocol.Notifications notifications, String amount, String fee, String outAddress) { mIsoDep = isoDep; mNotifications = notifications; txOutAddress = outAddress; SetTransactionValue(amount, fee); } @Override public void run() { if (mIsoDep == null) { return; } CardProtocol protocol = new CardProtocol(getBaseContext(), mIsoDep, mCard, mNotifications); mNotifications.OnReadStart(protocol); try { // for Samsung's bugs - // Workaround for the Samsung Galaxy S5 (since the // first connection always hangs on transceive). int timeout = mIsoDep.getTimeout(); mIsoDep.connect(); mIsoDep.close(); mIsoDep.connect(); mIsoDep.setTimeout(timeout); try { mNotifications.OnReadProgress(protocol, 5); Log.i("SignPaymentTask", "[-- Start sign payment --]"); if (isCancelled) return; protocol.run_VerifyCard(); Log.i("SignPaymentTask", "Manufacturer: " + protocol.getCard().getManufacturer().getOfficialName()); mNotifications.OnReadProgress(protocol, 30); if (isCancelled) return; // // if (mCard.getBlockchain() == Blockchain.Ethereum) { // SignETH_TX(protocol); // } else { // SignBTC_TX(protocol); // } CoinEngine engine = CoinEngineFactory.Create(mCard.getBlockchain()); if (engine != null) { if (mCard.getPauseBeforePIN2() > 0) { mNotifications.OnReadWait(mCard.getPauseBeforePIN2()); } byte[] tx; // try { tx = engine.Sign(txFee, txAmount, txOutAddress, mCard, protocol); // } // finally { // mNotifications.OnReadWait(0); // } if (tx != null) { String txStr = BTCUtils.toHex(tx); if (mCard.getBlockchain() == Blockchain.Ethereum || mCard.getBlockchain() == Blockchain.EthereumTestNet || mCard.getBlockchain() == Blockchain.Token) { txStr = String.format("0x%s", txStr); } LastSignStorage.setLastTX(mCard.getWallet(), txStr); Intent intent = new Intent(getBaseContext(), SendTransactionActivity.class); intent.putExtra("UID", mCard.getUID()); intent.putExtra("Card", mCard.getAsBundle()); intent.putExtra("TX", txStr); startActivityForResult(intent, REQUEST_CODE_SEND_PAYMENT); } } mNotifications.OnReadProgress(protocol, 100); if (isCancelled) return; } finally { mNfcManager.IgnoreTag(mIsoDep.getTag()); } } catch (CardProtocol.TangemException_InvalidPIN e) { e.printStackTrace(); protocol.setError(e); } catch (Exception e) { e.printStackTrace(); protocol.setError(e); } finally { Log.i("SignPaymentTask", "[-- Finish sign payment --]"); mNotifications.OnReadFinish(protocol); } } public void cancel(Boolean AllowInterrupt) { try { if (this.isAlive()) { isCancelled = true; join(500); } if (this.isAlive() && AllowInterrupt) { interrupt(); mNotifications.OnReadCancel(); } } catch (Exception e) { e.printStackTrace(); } } } public void OnReadStart(CardProtocol cardProtocol) { progressBar.post(new Runnable() { @Override public void run() { progressBar.setVisibility(View.VISIBLE); progressBar.setProgress(5); } }); } public void OnReadFinish(final CardProtocol cardProtocol) { signPaymentTask = null; if (cardProtocol != null) { if (cardProtocol.getError() == null) { progressBar.post(new Runnable() { @Override public void run() { progressBar.setProgress(100); progressBar.setProgressTintList(ColorStateList.valueOf(Color.GREEN)); } }); } else { lastReadSuccess = false; if (cardProtocol.getError().getClass().equals(CardProtocol.TangemException_InvalidPIN.class)) { progressBar.post(new Runnable() { @Override public void run() { progressBar.setProgress(100); progressBar.setProgressTintList(ColorStateList.valueOf(Color.RED)); } }); progressBar.postDelayed(new Runnable() { @Override public void run() { try { progressBar.setProgress(0); progressBar.setProgressTintList(ColorStateList.valueOf(Color.DKGRAY)); progressBar.setVisibility(View.INVISIBLE); Intent intent = new Intent(); intent.putExtra("message", "Cannot sign transaction. Make sure you enter correct PIN2!"); intent.putExtra("UID", cardProtocol.getCard().getUID()); intent.putExtra("Card", cardProtocol.getCard().getAsBundle()); setResult(RESULT_INVALID_PIN, intent); finish(); } catch (Exception e) { e.printStackTrace(); } } }, 500); return; } else { progressBar.post(new Runnable() { @Override public void run() { if (cardProtocol.getError() instanceof CardProtocol.TangemException_ExtendedLengthNotSupported) { if (!NoExtendedLengthSupportDialog.allreadyShowed) { new NoExtendedLengthSupportDialog().show(getFragmentManager(), "NoExtendedLengthSupportDialog"); } } else { Toast.makeText(getBaseContext(), "Try to scan again", Toast.LENGTH_LONG).show(); } progressBar.setProgress(100); progressBar.setProgressTintList(ColorStateList.valueOf(Color.RED)); } }); } } } progressBar.postDelayed(new Runnable() { @Override public void run() { try { progressBar.setProgress(0); progressBar.setProgressTintList(ColorStateList.valueOf(Color.DKGRAY)); progressBar.setVisibility(View.INVISIBLE); } catch (Exception e) { e.printStackTrace(); } } }, 500); } public void OnReadProgress(CardProtocol protocol, final int progress) { progressBar.post(new Runnable() { @Override public void run() { progressBar.setProgress(progress); } }); } public void OnReadCancel() { signPaymentTask = null; progressBar.postDelayed(new Runnable() { @Override public void run() { try { progressBar.setProgress(0); progressBar.setProgressTintList(ColorStateList.valueOf(Color.DKGRAY)); progressBar.setVisibility(View.INVISIBLE); } catch (Exception e) { e.printStackTrace(); } } }, 500); } @Override public void OnReadWait(final int msec) { WaitSecurityDelayDialog.OnReadWait(this, msec); } @Override public void OnReadBeforeRequest(int timeout) { WaitSecurityDelayDialog.onReadBeforeRequest(this, timeout); } @Override public void OnReadAfterRequest() { WaitSecurityDelayDialog.onReadAfterRequest(this); } @Override public boolean onKeyDown(int keyCode, KeyEvent event) { switch (keyCode) { case KeyEvent.KEYCODE_BACK: Intent intent = new Intent(); intent.putExtra("message", "Operation canceled by user"); setResult(Activity.RESULT_CANCELED, intent); finish(); return true; } return super.onKeyDown(keyCode, event); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode == REQUEST_CODE_SEND_PAYMENT) { setResult(resultCode, data); finish(); return; } super.onActivityResult(requestCode, resultCode, data); } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/wallet/SwapPINActivity.java b/app/src/main/java/com/tangem/wallet/SwapPINActivity.java new file mode 100644 index 0000000000..94b2342ee5 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/SwapPINActivity.java @@ -0,0 +1,326 @@ +package com.tangem.wallet; + +import android.app.Activity; +import android.content.Intent; +import android.content.res.ColorStateList; +import android.graphics.Color; +import android.nfc.NfcAdapter; +import android.nfc.Tag; +import android.nfc.tech.IsoDep; +import android.os.Bundle; +import android.support.v7.app.AppCompatActivity; +import android.util.Log; +import android.view.View; +import android.widget.ProgressBar; +import android.widget.TextView; +import android.widget.Toast; + +import com.tangem.cardReader.CardProtocol; +import com.tangem.cardReader.NfcManager; +import com.tangem.cardReader.Util; + +public class SwapPINActivity extends AppCompatActivity implements NfcAdapter.ReaderCallback, CardProtocol.Notifications { + + public static final int RESULT_INVALID_PIN = Activity.RESULT_FIRST_USER; + private Tangem_Card mCard; + private NfcManager mNfcManager; + private static final String logTag = "SwapPIN"; + private ProgressBar progressBar; + private SwapPINTask swapPinTask; + + private String newPIN, newPIN2; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_swap_pin); + + MainActivity.commonInit(getApplicationContext()); + + mCard = new Tangem_Card(getIntent().getStringExtra("UID")); + mCard.LoadFromBundle(getIntent().getExtras().getBundle("Card")); + + newPIN = getIntent().getStringExtra("newPIN"); + newPIN2 = getIntent().getStringExtra("newPIN2"); + + TextView tvCardID = findViewById(R.id.tvCardID); + tvCardID.setText(mCard.getCIDDescription()); + + mNfcManager = new NfcManager(this, this); + + progressBar = findViewById(R.id.progressBar); + progressBar.setProgressTintList(ColorStateList.valueOf(Color.DKGRAY)); + progressBar.setVisibility(View.INVISIBLE); + } + + @Override + public void onTagDiscovered(Tag tag) { + try { + // get IsoDep handle and run cardReader thread + final IsoDep isoDep = IsoDep.get(tag); + if (isoDep == null) { + throw new CardProtocol.TangemException(getString(R.string.wrong_tag_err)); + } + byte UID[] = tag.getId(); + String sUID = Util.byteArrayToHexString(UID); + Log.v(logTag, "UID: " + sUID); + + if (sUID.equals(mCard.getUID())) { + isoDep.setTimeout(mCard.getPauseBeforePIN2() + 65000); + swapPinTask = new SwapPINTask(isoDep, this); + swapPinTask.start(); + } else { + Log.d(logTag, "Mismatch card UID (" + sUID + " instead of " + mCard.getUID() + ")"); + mNfcManager.IgnoreTag(isoDep.getTag()); + } + + } catch (Exception e) { + e.printStackTrace(); + } + + } + + @Override + public void onResume() { + super.onResume(); + mNfcManager.onResume(); + } + + @Override + public void onPause() { + mNfcManager.onPause(); + if (swapPinTask != null) { + swapPinTask.cancel(true); + } + super.onPause(); + } + + @Override + public void onStop() { + // dismiss enable NFC dialog + mNfcManager.onStop(); + if (swapPinTask != null) { + swapPinTask.cancel(true); + } + super.onStop(); + } + + private class SwapPINTask extends Thread { + + IsoDep mIsoDep; + CardProtocol.Notifications mNotifications; + private boolean isCancelled = false; + + SwapPINTask(IsoDep isoDep, CardProtocol.Notifications notifications) { + mIsoDep = isoDep; + mNotifications = notifications; + } + + @Override + public void run() { + if (mIsoDep == null) { + return; + } + CardProtocol protocol = new CardProtocol(getBaseContext(), mIsoDep, mCard, mNotifications); + + mNotifications.OnReadStart(protocol); + try { + + // for Samsung's bugs - + // Workaround for the Samsung Galaxy S5 (since the + // first connection always hangs on transceive). + int timeout = mIsoDep.getTimeout(); + mIsoDep.connect(); + mIsoDep.close(); + mIsoDep.connect(); + mIsoDep.setTimeout(timeout); + try { + + mNotifications.OnReadProgress(protocol, 5); + + Log.i("SwapTask", "[-- Start swap pin --]"); + + if (isCancelled) return; + + if (mCard.getPauseBeforePIN2() > 0) { + mNotifications.OnReadWait(mCard.getPauseBeforePIN2()); + } + +// try { + protocol.run_SwapPIN(PINStorage.getPIN2(), newPIN, newPIN2, false); + protocol.setPIN(newPIN); + mCard.setPIN(newPIN); +// } finally { +// mNotifications.OnReadWait(0); +// } + + mNotifications.OnReadProgress(protocol, 50); + + protocol.run_Read(); + + mNotifications.OnReadProgress(protocol, 100); + + } finally { + mNfcManager.IgnoreTag(mIsoDep.getTag()); + } + } catch (Exception e) { + e.printStackTrace(); + protocol.setError(e); + + } finally { + Log.i("SwapPINTask", "[-- Finish purge --]"); + mNotifications.OnReadFinish(protocol); + } + } + + public void cancel(Boolean AllowInterrupt) { + try { + if (this.isAlive()) { + isCancelled = true; + join(500); + } + if (this.isAlive() && AllowInterrupt) { + interrupt(); + mNotifications.OnReadCancel(); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + } + + + public void OnReadStart(CardProtocol cardProtocol) { + progressBar.post(new Runnable() { + @Override + public void run() { + progressBar.setVisibility(View.VISIBLE); + progressBar.setProgress(5); + } + }); + } + + public void OnReadFinish(final CardProtocol cardProtocol) { + + swapPinTask = null; + + if (cardProtocol != null) { + if (cardProtocol.getError() == null) { + progressBar.post(new Runnable() { + @Override + public void run() { + progressBar.setProgress(100); + progressBar.setProgressTintList(ColorStateList.valueOf(Color.GREEN)); + Intent intent = new Intent(); + intent.putExtra("UID", cardProtocol.getCard().getUID()); + intent.putExtra("Card", cardProtocol.getCard().getAsBundle()); + setResult(Activity.RESULT_OK, intent); + finish(); + } + }); + } else if (cardProtocol.getError() instanceof CardProtocol.TangemException_InvalidPIN) { + progressBar.post(new Runnable() { + @Override + public void run() { + progressBar.setProgress(100); + progressBar.setProgressTintList(ColorStateList.valueOf(Color.RED)); + } + }); + progressBar.postDelayed(new Runnable() { + @Override + public void run() { + try { + progressBar.setProgress(0); + progressBar.setProgressTintList(ColorStateList.valueOf(Color.DKGRAY)); + progressBar.setVisibility(View.INVISIBLE); + Intent intent = new Intent(); + intent.putExtra("message", "Cannot change PIN(s). Make sure you enter correct PIN2!"); + intent.putExtra("UID", cardProtocol.getCard().getUID()); + intent.putExtra("Card", cardProtocol.getCard().getAsBundle()); + setResult(RESULT_INVALID_PIN, intent); + finish(); + } catch (Exception e) { + e.printStackTrace(); + } + } + }, 500); + return; + } else { + + progressBar.post(new Runnable() { + @Override + public void run() { + if (cardProtocol.getError() instanceof CardProtocol.TangemException_ExtendedLengthNotSupported) { + if (!NoExtendedLengthSupportDialog.allreadyShowed) { + new NoExtendedLengthSupportDialog().show(getFragmentManager(), "NoExtendedLengthSupportDialog"); + } + } else { + Toast.makeText(getBaseContext(), "Try to scan again", Toast.LENGTH_LONG).show(); + } + progressBar.setProgress(100); + progressBar.setProgressTintList(ColorStateList.valueOf(Color.RED)); + } + }); + + } + + progressBar.postDelayed(new Runnable() { + @Override + public void run() { + try { + progressBar.setProgress(0); + progressBar.setProgressTintList(ColorStateList.valueOf(Color.DKGRAY)); + progressBar.setVisibility(View.INVISIBLE); + } catch (Exception e) { + e.printStackTrace(); + } + } + }, 500); + } + } + + public void OnReadProgress(CardProtocol protocol, final int progress) { + progressBar.post(new Runnable() { + @Override + public void run() { + progressBar.setProgress(progress); + } + }); + } + + public void OnReadCancel() { + + swapPinTask = null; + + progressBar.postDelayed(new Runnable() { + @Override + public void run() { + try { + progressBar.setProgress(0); + progressBar.setProgressTintList(ColorStateList.valueOf(Color.DKGRAY)); + progressBar.setVisibility(View.INVISIBLE); + } catch (Exception e) { + e.printStackTrace(); + } + } + }, 500); + } + + @Override + public void OnReadWait(final int msec) { + WaitSecurityDelayDialog.OnReadWait(this, msec); + } + + @Override + public void OnReadBeforeRequest(int timeout) { + WaitSecurityDelayDialog.onReadBeforeRequest(this, timeout); + } + + @Override + public void OnReadAfterRequest() { + WaitSecurityDelayDialog.onReadAfterRequest(this); + } + +} + diff --git a/app/src/main/java/com/tangem/wallet/Tangem_Card.java b/app/src/main/java/com/tangem/wallet/Tangem_Card.java new file mode 100644 index 0000000000..589761a3ae --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/Tangem_Card.java @@ -0,0 +1,1365 @@ +package com.tangem.wallet; + +import android.os.Bundle; +import android.util.Log; + +import com.google.common.base.Strings; +import com.tangem.cardReader.SettingsMask; +import com.tangem.cardReader.Util; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.math.RoundingMode; +import java.text.DecimalFormat; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Date; +import java.util.List; +import java.util.StringJoiner; + +/** + * Created by dvol on 16.07.2017. + */ + +public class Tangem_Card { + private byte[] CID; + private Status status; + private String wallet; + private String UID; + private String error; + private String message; + private String balanceDecimal; + private String balanceDecimalAlter = ""; + private Long balanceConfirmed, balanceUnconfirmed; + private String blockchainID; + private Manufacturer manufacturer = Manufacturer.Unknown; + private boolean manufacturerConfirmed = false; + private int maxSignatures; + private int remainingSignatures; + private String PIN; + private byte[] pbWalletKey = null; + private byte[] pbCardKey = null; + private byte[] pbWalletKeyRar = null; + private Date dtPersonalization = null; + private float rate = 0; + private float rateAlter = 0; + +// public static final int SettingsMask_IsReusable = 0x0001; +// public static final int SettingsMask_UseRecovery = 0x0002; +// public static final int SettingsMask_UseBlock = 0x0004; +// +// public static final int SettingsMask_AllowSwapPIN = 0x0010; +// public static final int SettingsMask_AllowSwapPIN2 = 0x0020; +// public static final int SettingsMask_UseCVC = 0x0040; +// +// public static final int SettingsMask_UseOneCommandAtTime = 0x100; +// public static final int SettingsMask_UseNDEF = 0x200; +// public static final int SettingsMask_UseDynamicNDEF = 0x400; + + private BigInteger countConfirmTX = null; + + public BigInteger GetConfirmTXCount() { + if (countConfirmTX == null) { + countConfirmTX = BigInteger.valueOf(0); + } + return countConfirmTX; + } + + public void SetConfirmTXCount(BigInteger count) { + countConfirmTX = count; + } + + public float getRate() { + return rate; + } + + public float getRateAlter() { + return rateAlter; + } + + public void setRate(float rate) { + this.rate = rate; + } + + public void setRateAlter(float rate) { + this.rateAlter = rate; + } + + public String getBlockchainID() { + return blockchainID; + } + + public Blockchain getBlockchain() { + //TODO: hard fork only for test + //return Blockchain.Ethereum; + return Blockchain.fromId(blockchainID); + } + + public void setBlockchainID(String blockchainID) { + this.blockchainID = blockchainID; + } + + public void addTokenToBlockchainName() { + String token = getTokenSymbol(); + if (Strings.isNullOrEmpty(token)) + return; + String oldName = getBlockchain().getOfficialName(); + String newName = String.format("%s (%s)", oldName, token); + setBlockchainName(newName); + } + + private String blochchainName = ""; + + public void setBlockchainName(String name) { + blochchainName = name; + } + + + public String getBlockchainName() { + if (Strings.isNullOrEmpty(blochchainName)) + return getBlockchain().getOfficialName(); + return blochchainName; + } + + public void setBlockchainIDFromCard(String blockchainID) { + + //TODO: ONLY FOR TEST + +// if(Blockchain.fromId(blockchainID) == Blockchain.Bitcoin) +// { +// this.blockchainID = "BCH"; +// return; +// } +// +// if(Blockchain.fromId(blockchainID) == Blockchain.BitcoinTestNet) +// { +// this.blockchainID = "BCH/test"; +// return; +// } + + + if (Blockchain.fromId(blockchainID) != Blockchain.Ethereum && Blockchain.fromId(blockchainID) != Blockchain.EthereumTestNet) + this.blockchainID = blockchainID; + + if (isToken()) { + this.blockchainID = Blockchain.Token.getID(); + addTokenToBlockchainName(); + } else { + this.blockchainID = blockchainID; + } + + + } + + public void setWalletPublicKey(byte[] publicKey) { + pbWalletKey = publicKey; + } + + public void setWalletPublicKeyRar(byte[] publicKey) { + pbWalletKeyRar = publicKey; + } + + public byte[] getWalletPublicKey() { + return pbWalletKey; + } + + public byte[] getWalletPublicKeyRar() { + return pbWalletKeyRar; + } + + private boolean walletPublicKeyValid = false; + + public void setWalletPublicKeyValid(boolean walletPublicKeyValid) { + this.walletPublicKeyValid = walletPublicKeyValid; + } + + public boolean isWalletPublicKeyValid() { + return walletPublicKeyValid; + } + + + public void setCardPublicKey(byte[] publicKey) { + pbCardKey = publicKey; + } + + public byte[] getCardPublicKey() { + return pbCardKey; + } + + private boolean cardPublicKeyValid = false; + + public void setCardPublicKeyValid(boolean cardPublicKeyValid) { + this.cardPublicKeyValid = cardPublicKeyValid; + } + + public boolean isCardPublicKeyValid() { + return cardPublicKeyValid; + } + + public Double AmountFromInternalUnits(Long internalAmount) { + return ((double) internalAmount) / getBlockchain().getMultiplier(); + } + + private Double AmountFromInternalUnits(Integer internalAmount) { + return ((double) internalAmount) / getBlockchain().getMultiplier(); + } + + // TODO разобраться с балансами, привести к единому интерфейсу + public Long InternalUnitsFromString(String caption) { + try { + return FormatUtil.ConvertStringToLong(caption); + } catch (Exception e) { + return null; + } + } + + + public String getAmountDescription(Double amount) { + if (amount < 10) { + amount *= 1000.0; //todo: really? + //String pattern = "#0.000"; // If you like 4 zeros + //DecimalFormat myFormatter = new DecimalFormat(pattern); + //String output = myFormatter.format(amount); + + String output = FormatUtil.DoubleToString(amount); + + return output + " m" + getBlockchain().getCurrency(); + + //return String.format("%.3f m%s", amount, getBlockchain().getCurrency()); + } else { + //return String.format("%.3f %s", amount, getBlockchain().getCurrency()); + String output = FormatUtil.DoubleToString(amount); + return output + " " + getBlockchain().getCurrency(); + + } + } + + public String getAmountEquivalentDescription(Double amount) { + if (getBlockchain() == Blockchain.Ethereum || getBlockchain() == Blockchain.EthereumTestNet || getBlockchain() == Blockchain.Token) { + return EthEngine.getAmountEquivalentDescriptionETH(amount, rate); + } + + return BtcEngine.getAmountEquivalentDescriptionBTC(amount, rate); + } + + public boolean getAmountEquivalentDescriptionAvailable() { + return rate > 0; + } + + public String getAmountInGwei(String amount) { + BigInteger d = new BigInteger(amount, 10); + BigInteger m = d.divide(BigInteger.valueOf(1000000000L)); + return m.toString(10); + } + + public Manufacturer getManufacturer() { + return manufacturer; + } + + public boolean isManufacturerConfirmed() { + return manufacturerConfirmed; + } + + public void setManufacturer(Manufacturer manufacturer, boolean verified) { + if (this.manufacturer == manufacturer) { + this.manufacturerConfirmed |= verified; + } else { + this.manufacturer = manufacturer; + this.manufacturerConfirmed = verified; + } + } + + public int getRemainingSignatures() { + return remainingSignatures; + } + + public void setRemainingSignatures(int remainingSignatures) { + this.remainingSignatures = remainingSignatures; + } + + public String getTransactionList() { + try { + StringBuffer result = new StringBuffer(); + if (hasUnspentInfo()) { + if (mUnspentTransactions.size() > 0) { + + for (int i = 0; i < mUnspentTransactions.size(); ++i) { + if (i != 0) { + result.append(" \n"); + } + result.append(String.valueOf(i + 1)); + result.append(": "); + long unix_time = GetTimestamp(mUnspentTransactions.get(i).Height); + + if (unix_time < UNIX_TIME_2017_01_01) + return "-- -- --"; + unix_time = unix_time * 1000L; + + Date dt = new Date(unix_time); + //java.util.Calendar cd = Calendar.getInstance(); + //cd.setTimeInMillis(unix_time); + + //result.append(String.format("%02d/%02d/%04d", cd.get(Calendar.DAY_OF_MONTH), cd.get(Calendar.MONTH) + 1, cd.get(Calendar.YEAR))); + + result.append(Util.formatDate(dt)); + + + result.append(" - Amount: "); + result.append(getAmountDescription(AmountFromInternalUnits(mUnspentTransactions.get(i).Amount))); + //result.append(String.valueOf((double) mUnspentTransactions.get(i).Amount / 100000)); + //result.append(",\nTx: "); This is only for developers + //result.append(String.valueOf(mUnspentTransactions.get(i).txID)); + + } + +// if (hasHistoryInfo()) { +// for (int i = 0; i < mHistoryTransactions.size(); ++i) { +// result.append(" \n"); +// result.append("O"); +// result.append(String.valueOf(i + 1)); +// result.append(": "); +// long unix_time = GetTimestamp(mHistoryTransactions.get(i).Height); +// unix_time = unix_time * 1000L; +// +// java.util.Calendar cd = Calendar.getInstance(); +// cd.setTimeInMillis(unix_time); +// +// result.append(String.format("%02d/%02d/%04d", cd.get(Calendar.DAY_OF_MONTH), cd.get(Calendar.MONTH) + 1, cd.get(Calendar.YEAR))); +// +// +// } +// } + } else { + result.append("None"); + } + } else { + result.append("-- -- --"); + } + if (balanceUnconfirmed != 0) { + result.append("awaiting confirmation..."); + } + return result.toString(); + } catch (Exception e) { + return "?"; + } + } + + public String getInputsDescription() { + + try { + StringBuffer result = new StringBuffer(); + if (hasUnspentInfo()) { + if (mUnspentTransactions.size() == 1) { + // + if (balanceUnconfirmed != 0) { + result.append(getAmountDescription(AmountFromInternalUnits(mUnspentTransactions.get(0).Amount))); + } else { + result.append("1 unspent"); + } + long unix_time = GetTimestamp(mUnspentTransactions.get(0).Height); + + if (unix_time < UNIX_TIME_2017_01_01) { + + } else { + result.append(", "); + unix_time = unix_time * 1000L; + result.append(Util.formatDateTime(new Date(unix_time))); + } + + +// java.util.Calendar cd = Calendar.getInstance(); +// cd.setTimeInMillis(unix_time); + + //result.append(String.format(" at %02d/%02d/%04d %02d:%02d:%02d", cd.get(Calendar.DAY_OF_MONTH), cd.get(Calendar.MONTH) + 1, cd.get(Calendar.YEAR), cd.get(Calendar.HOUR), cd.get(Calendar.MINUTE), cd.get(Calendar.SECOND))); + + + if (balanceUnconfirmed != 0) { + result.append("\nawaiting confirmation..."); + } + } else if (mUnspentTransactions.size() > 0) { + long minDate = GetTimestamp(mUnspentTransactions.get(0).Height) * 1000L; + long maxDate = minDate; + int unspentAmount = mUnspentTransactions.get(0).Amount; + + for (int i = 1; i < mUnspentTransactions.size(); ++i) { + long unix_time = GetTimestamp(mUnspentTransactions.get(i).Height); + unix_time = unix_time * 1000L; + + if (unix_time < minDate) minDate = unix_time; + if (unix_time > maxDate) maxDate = unix_time; + unspentAmount += mUnspentTransactions.get(i).Amount; + } + + Calendar cd = Calendar.getInstance(); + cd.setTimeInMillis(minDate); + if (balanceUnconfirmed != 0) { + result.append(getAmountDescription(AmountFromInternalUnits(unspentAmount))); + result.append(" in "); + } + result.append(String.valueOf(mUnspentTransactions.size())); + result.append(" unspents\n"); + + result.append(String.format("between %s\n", Util.formatDateTime(new Date(minDate)))); + + cd.setTimeInMillis(maxDate); + result.append(String.format("and %s", Util.formatDateTime(new Date(maxDate)))); + + if (balanceUnconfirmed != 0) { + result.append("\nawaiting confirmation..."); + } + } else if (balanceUnconfirmed == 0) { + result.append("None"); + } else if (balanceUnconfirmed != 0) { + result.append("awaiting confirmation..."); + } + } else { + result.append("-- -- --"); + } + return result.toString(); + } catch (Exception e) { + return "?"; + } + } + + public int getCountOutputs() { + int count = 0; + for (int i = 0; i < mHistoryTransactions.size(); ++i) { + if (mHistoryTransactions.get(i).isInput) + continue; + ++count; + } + return count; + } + + public String getOutputsDescription() { + if (hasUnspentInfo() && hasHistoryInfo() && mHistoryTransactions.size() - mUnspentTransactions.size() > 0) { + return String.valueOf(mHistoryTransactions.size() - mUnspentTransactions.size()); + } else if (hasUnspentInfo() && hasHistoryInfo()) { + return "None"; + } else { + return "-- -- --"; + } + } + + static int UNIX_TIME_2017_01_01 = 1483228800; + + public String getLastInputDescription() { + + if (hasUnspentInfo()) { + if (mUnspentTransactions.size() > 0) { + + long maxUDT = 0; + int UnspentTransactionsAmount = 0; + + for (int i = 0; i < mUnspentTransactions.size(); ++i) { + UnspentTransactionsAmount += mUnspentTransactions.get(i).Amount; + long unix_time = GetTimestamp(mUnspentTransactions.get(i).Height); + + if (unix_time < UNIX_TIME_2017_01_01) { + return "-- -- --"; + } + unix_time = unix_time * 1000L; + if (unix_time > maxUDT) maxUDT = unix_time; + } + + Calendar cd = Calendar.getInstance(); + cd.setTimeInMillis(maxUDT); + + StringBuilder sb = new StringBuilder(); + //sb.append(String.format("%02d/%02d/%04d %02d:%02d:%02d", cd.get(Calendar.DAY_OF_MONTH), cd.get(Calendar.MONTH) + 1, cd.get(Calendar.YEAR), cd.get(Calendar.HOUR), cd.get(Calendar.MINUTE), cd.get(Calendar.SECOND))); + sb.append(Util.formatDateTime(new Date(maxUDT))); + + if (balanceUnconfirmed != null && balanceUnconfirmed != 0) { + return "awaiting confirmation..."; + } + return sb.toString(); + + } else { + if (balanceUnconfirmed != null) { + if (balanceUnconfirmed != 0) { + return "awaiting confirmation..."; + } else { + return "None"; + } + } + } + } + return "-- -- --"; + + } + + public String getLastOutputDescription() { + if (hasUnspentInfo() && hasHistoryInfo()) { + if (mHistoryTransactions.size() - mUnspentTransactions.size() > 0) { + long maxUDT = 0; + + for (int i = 0; i < mHistoryTransactions.size(); ++i) { + if (!mHistoryTransactions.get(i).isInput) + continue; + if (indexfOfUnspentTransaction(mHistoryTransactions.get(i).txID) != -1) + continue; + long unix_time = GetTimestamp(mHistoryTransactions.get(i).Height); + if (unix_time == 1296688602L) + unix_time = 0; + if (unix_time < UNIX_TIME_2017_01_01) + return "-- -- --"; + unix_time = unix_time * 1000L; + if (unix_time > maxUDT) maxUDT = unix_time; + } + + if (maxUDT == 0) { + return "None"; + } else { + return Util.formatDateTime(new Date(maxUDT)); + } + + } else { + return "None"; + } + } else { + return "-- -- --"; + } + } + + private int indexfOfUnspentTransaction(String txID) { + if (hasUnspentInfo()) { + for (int i = 0; i < mUnspentTransactions.size(); i++) { + if (mUnspentTransactions.get(i).txID.equals(txID)) return i; + } + } + return -1; + } + + public String getLastSignedDescription() { + Date dt = LastSignStorage.getLastSignDate(getWallet()); + if (dt != null) { + return Util.formatDateTime(dt); + + } else { + return "Not supported"; + } + } + + public String getPIN() { + return PIN; + } + + public void setPIN(String PIN) { + this.PIN = PIN; + } + + public void clearInfo() { + balanceConfirmed = null; + balanceUnconfirmed = null; + mUnspentTransactions = null; + mHistoryTransactions = null; + } + + public Date getPersonalizationDateTime() { + return dtPersonalization; + } + + public void setPersonalizationDateTime(Date dtPersonalization) { + this.dtPersonalization = dtPersonalization; + } + + public String getPersonalizationDateTimeDescription() { + return Util.formatDate(dtPersonalization); + + } + + private int health = 0; + + public void setHealth(int health) { + if (health > this.health) this.health = health; + } + + public boolean isHealthOK() { + return health == 0; + } + + private Issuer issuer = Issuer.Unknown; + + public void setIssuer(Issuer issuer) { + this.issuer = issuer; + } + + String contractAddress = ""; + + public void setContractAddress(String address) { + contractAddress = address; + } + + public String getContractAddress() { + return contractAddress; + } + + String tokenSymbol = ""; + + public void setTokenSymbol(String symbol) { + tokenSymbol = symbol; + } + + public String getTokenSymbol() { + return tokenSymbol; + } + + public boolean isToken() { + return !Strings.isNullOrEmpty(tokenSymbol); + } + + int tokensDecimal = 18; + + public void setTokensDecimal(int tokensDecimal) { + this.tokensDecimal = tokensDecimal; + } + + public int getTokensDecimal() { + return tokensDecimal; + } + + + public Issuer getIssuer() { + return issuer; + } + + public String getIssuerDescription() { + return issuer.getOfficialName(); + } + + byte[] issuerData; + byte[] issuerDataSignature; + + public byte[] getIssuerData() { + return issuerData; + } + + public byte[] getIssuerDataSignature() { + return issuerDataSignature; + } + + public void setIssuerData(byte[] value, byte[] signature) { + issuerData = value; + } + + boolean needWriteIssuerData = false; + + public boolean getNeedWriteIssuerData() { + return needWriteIssuerData; + } + + public void setNeedWriteIssuerData(boolean value) { + needWriteIssuerData = value; + } + + public String getIssuerDataDescription() { + return ""; + } + + + private int pauseBeforePIN2 = 0; + + public void setPauseBeforePIN2(int value) { + this.pauseBeforePIN2 = value; + } + + public int getPauseBeforePIN2() { + return pauseBeforePIN2; + } + + private Integer settingsMask = null; + + public void setSettingsMask(int settingsMask) { + this.settingsMask = settingsMask; + } + + public Boolean isReusable() { + if (settingsMask == null) return null; + return (settingsMask.intValue() & SettingsMask.IsReusable) != 0; + } + + public Boolean allowSwapPIN() { + if (settingsMask == null) return null; + return (settingsMask.intValue() & SettingsMask.AllowSwapPIN) != 0; + } + + public Boolean allowSwapPIN2() { + if (settingsMask == null) return null; + return (settingsMask.intValue() & SettingsMask.AllowSwapPIN2) != 0; + } + + public Boolean needCVC() { + if (settingsMask == null) return null; + return (settingsMask.intValue() & SettingsMask.UseCVC) != 0; + } + + public Boolean useDefaultPIN1() { + return PINStorage.isDefaultPIN(getPIN()); + } + + public Boolean useSmartSecurityDelay() { + if (settingsMask == null) return null; + return (settingsMask.intValue() & SettingsMask.SmartSecurityDelay) != 0; } + + public enum PIN2_Mode {Unchecked, DefaultPIN2, CustomPIN2} + + public PIN2_Mode PIN2 = PIN2_Mode.Unchecked; + + public Boolean useDefaultPIN2() { + if (PIN2 == PIN2_Mode.DefaultPIN2 || (PIN2 == PIN2_Mode.Unchecked && (needCVC() || (getPauseBeforePIN2() > 0)))) { + // define that we use default PIN2 if we try it or not try and security delay or CVC is used + return true; + } else { + return false; + } + } + + public void setUseDefaultPIN2(Boolean value) { + if (value != null) { + PIN2 = value ? PIN2_Mode.DefaultPIN2 : PIN2_Mode.CustomPIN2; + } else { + PIN2 = PIN2_Mode.Unchecked; + } + } + + public Boolean supportNDEF() { + if (settingsMask == null) return null; + return (settingsMask.intValue() & SettingsMask.UseNDEF) != 0; + } + + public Boolean supportOnlyOneCommandAtTime() { + if (settingsMask == null) return null; + return supportNDEF() && ((settingsMask.intValue() & SettingsMask.UseOneCommandAtTime) != 0); + } + + public Boolean supportDynamicNDEF() { + if (settingsMask == null) return null; + return supportNDEF() && ((settingsMask.intValue() & SettingsMask.UseDynamicNDEF) != 0); + } + + public Boolean supportBlock() { + if (settingsMask == null) return null; + return (settingsMask.intValue() & SettingsMask.UseBlock) != 0; + } + + public int getMaxSignatures() { + return maxSignatures; + } + + public void setMaxSignatures(int value) { + maxSignatures = value; + } + + private String firmwareVersion; + + public void setFirmwareVersion(String firmwareVersion) { + this.firmwareVersion = firmwareVersion; + } + + public String getFirmwareVersion() { + return firmwareVersion; + } + + public Boolean useDevelopersFirmware() { + return getFirmwareVersion().endsWith("d") || getFirmwareVersion().endsWith("SDK"); + } + + public static String getFirmwareVersionNumber(String version) throws Exception { + if (version == null || version.length() < 4) { + throw new Exception("Firmware version has unsupported format!"); + } + if (version.endsWith("d SDK")) { + return version.substring(0, version.length() - 5); + } else if (version.endsWith("r")) { + return version.substring(0, version.length() - 1); + } else { + return version; + } + } + + public static int[] getFirmwareVersionNumbers(String version) throws Exception { + String fwNumber = getFirmwareVersionNumber(version); + String[] strNumbers = fwNumber.split("\\."); + + if (strNumbers.length != 2) throw new Exception("Firmware version has unsupported format!"); + try { + int major = Integer.parseInt(strNumbers[0]), minor = Integer.parseInt(strNumbers[1]); + return new int[]{major, minor}; + } catch (NumberFormatException e) { + e.printStackTrace(); + throw new Exception("Firmware version has unsupported format!"); + } + } + + public Boolean isFirmwareOlder(String version) throws Exception { + int[] numbers1 = getFirmwareVersionNumbers(firmwareVersion), numbers2 = getFirmwareVersionNumbers(version); + return numbers1[0] < numbers2[0] || (numbers1[0] == numbers2[0] && numbers1[1] < numbers2[1]); + } + + public Boolean isFirmwareNewer(String version) throws Exception { + int[] numbers1 = getFirmwareVersionNumbers(firmwareVersion), numbers2 = getFirmwareVersionNumbers(version); + return numbers1[0] > numbers2[0] || (numbers1[0] == numbers2[0] && numbers1[1] > numbers2[1]); + } + + private String validationNodeDescription = ""; + + public String getValidationNodeDescription() { + return validationNodeDescription; + } + + public void setValidationNodeDescription(String validationNodeDescription) { + this.validationNodeDescription = validationNodeDescription; + } + + public enum SigningMethod { + Sign_Hash(0, "Sign hash"), + Sign_Raw(1, "Sign raw tx"), + Sign_Hash_Validated_By_Issuer(2, "Sign hash validated by issuer"), + Sign_Raw_Validated_By_Issuer(3, "Sign raw tx validated by issuer"); + + int ID; + String mDescription; + + SigningMethod(int ID, String description) { + this.ID = ID; + mDescription = description; + } + + static SigningMethod FindByID(int ID) { + SigningMethod[] methods = values(); + for (SigningMethod m : methods) { + if (m.ID == ID) return m; + } + return SigningMethod.Sign_Hash; + } + + public String getDescription() { + return mDescription; + } + } + + private SigningMethod signingMethod; + + public void setSigningMethod(int signingMethodID) { + this.signingMethod = SigningMethod.FindByID(signingMethodID); + } + + public SigningMethod getSigningMethod() { + return signingMethod; + } + + public static class HeaderInfo { + public HeaderInfo() { + + } + + public HeaderInfo(Integer height, Integer timestamp) { + Height = height; + TmStamp = timestamp; + } + + public Integer TmStamp; + public Integer Height; + + public Bundle getAsBundle() { + Bundle B = new Bundle(); + B.putInt("TmStamp", TmStamp); + B.putInt("Height", Height); + return B; + } + + public void LoadFromBundle(Bundle B) { + TmStamp = B.getInt("TmStamp"); + Height = B.getInt("Height"); + } + } + + public static class UnspentTransaction { + public String txID; + public Integer Amount; + public Integer Height; + public String Raw = ""; + + public Bundle getAsBundle() { + Bundle B = new Bundle(); + B.putString("txID", txID); + B.putInt("Amount", Amount); + B.putInt("Height", Height); + B.putString("Raw", Raw); + return B; + } + + public void LoadFromBundle(Bundle B) { + txID = B.getString("txID"); + Amount = B.getInt("Amount"); + Height = B.getInt("Height"); + Raw = B.getString("Raw"); + + } + } + + public static int CountOurTx(List listHTx) { + int count = 0; + for (HistoryTransaction tx : listHTx) { + + if (tx.Raw == null) + continue; + + String raw = tx.Raw; + //Log.e("H", tx.txID + " - ?"); + try { + ArrayList prevHashes = BTCUtils.getPrevTX(raw); + + boolean isOur = false; + for (byte[] hash : prevHashes) { + String checkID = BTCUtils.toHex(hash); + + for (HistoryTransaction txForCheck : listHTx) { + String id = txForCheck.txID; + //Log.e("H:", checkID + " - " + id); + if (id.equals(checkID)) { + isOur = true; + //Log.e("H", tx.txID + " - Complete"); + break; + } + } + if (isOur) { + ++count; + break; + } + } + + tx.isInput = !isOur; + } catch (BitcoinException e) { + e.printStackTrace(); + } + } + return count; + } + + public static class HistoryTransaction { + public String txID; + public Integer Height; + public String Raw = ""; + public boolean isInput = false; + + public Bundle getAsBundle() { + Bundle B = new Bundle(); + B.putString("txID", txID); + B.putInt("Height", Height); + B.putString("Raw", Raw); + B.putBoolean("Input", isInput); + return B; + } + + public void LoadFromBundle(Bundle B) { + txID = B.getString("txID"); + Height = B.getInt("Height"); + Raw = B.getString("Raw"); + isInput = B.getBoolean("Input"); + } + } + + private List mUnspentTransactions = null; + private List mHistoryTransactions = null; + private List mHeaders = null; + + public List getUnspentTransactions() { + if (mUnspentTransactions == null) mUnspentTransactions = new ArrayList<>(); + return mUnspentTransactions; + } + + public boolean hasUnspentInfo() { + return mUnspentTransactions != null; + } + + public List getHistoryTransactions() { + if (mHistoryTransactions == null) mHistoryTransactions = new ArrayList<>(); + return mHistoryTransactions; + } + + public List getOutputTransactions() { + if (mHistoryTransactions == null) mHistoryTransactions = new ArrayList<>(); + return mHistoryTransactions; + } + + public boolean hasHistoryInfo() { + return mHistoryTransactions != null; + } + + public List getHaedersInfo() { + if (mHeaders == null) mHeaders = new ArrayList<>(); + return mHeaders; + } + + public void UpdateHeaderInfo(HeaderInfo info) { + boolean newItem = true; + for (int i = 0; i < mHeaders.size(); ++i) { + if (mHeaders.get(i).Height == info.Height) { + newItem = false; + break; + } + } + + if (newItem) { + mHeaders.add(info); + } + } + + public int GetTimestamp(int height) { + if(mHeaders == null) + return 0; + for (int i = 0; i < mHeaders.size(); ++i) { + if (mHeaders.get(i).Height == height) { + return mHeaders.get(i).TmStamp; + } + } + + return 0; + } + + public boolean hasHeaderInfo() { + return mHeaders != null; + } + + public Tangem_Card(String UID) { + this.UID = UID; + } + + public void setStatus(Status status) { + this.status = status; + } + + public Status getStatus() { + return status; + } + + public byte[] getCID() { + return CID; + } + + public void setCID(byte[] value) { + this.CID = value; + } + + public String getCIDDescription() { + String strCID = Util.bytesToHex(CID); + try { + return strCID.substring(0, 4) + " " + strCID.substring(4, 8) + " " + strCID.substring(8, 12) + " " + strCID.substring(12, 16); + } catch (Exception e) { + return strCID; + } + } + + public void setWallet(String wallet) { + this.wallet = wallet; + } + + public String getWallet() { + return wallet; + } + + public String getShortWalletString() { + if (wallet.length() < 22) { + return wallet; + } else { + return wallet.substring(0, 10) + "......" + wallet.substring(wallet.length() - 10, wallet.length()); + } + } + + public String getUID() { + return UID; + } + + public void setUID(String UID) { + this.UID = UID; + } + + public Long getBalance() { + return balanceConfirmed + balanceUnconfirmed; + } + + public Long getBalanceUnconfirmed() { + return balanceUnconfirmed; + } + + public void setBalanceConfirmed(Long balance) { + this.balanceConfirmed = balance; + } + + public void setBalanceUnconfirmed(Long balance) { + this.balanceUnconfirmed = balance; + } + + public boolean hasBalanceInfo() { + return balanceConfirmed != null && balanceUnconfirmed != null; + } + + public String getDecimalBalance() { + return balanceDecimal; + } + + public String getDecimalBalanceAlter() { + return balanceDecimalAlter; + } + + public void setDecimalBalance(String value) { + balanceDecimal = value; + } + + public void setDecimalBalanceAlter(String value) { + balanceDecimalAlter = value; + } + + private byte[] offlineBalance; + + public void setOfflineBalance(byte[] offlineBalance) { + this.offlineBalance = offlineBalance; + } + + public byte[] getOfflineBalance() { + return offlineBalance; + } + + public void clearOfflineBalance() { + offlineBalance = null; + } + + public void setError(String error) { + this.error = error; + } + + public String getError() { + return error; + } + + public void setMessage(String value) { + this.message = value; + } + + public String getMessage() { + return message; + } + + public enum Status { + NotPersonalized(0), Empty(1), Loaded(2), Purged(3); + + Status(int Code) { + mCode = Code; + } + + private int mCode; + + public int getCode() { + return mCode; + } + + public static Status fromCode(int code) { + for (Status s : values()) { + if (s.getCode() == code) return s; + } + return null; + } + } + + public Bundle getAsBundle() { + Bundle B = new Bundle(); + SaveToBundle(B); + return B; + } + + public void SaveToBundle(Bundle B) { + B.putString("UID", UID); + B.putByteArray("CID", CID); + B.putString("PIN", PIN); + B.putString("PIN2", PIN2.name()); + B.putString("Status", status.name()); + B.putString("Blockchain", blockchainID); + B.putString("BlockchainName", blochchainName); + B.putInt("TokensDecimal", tokensDecimal); + B.putString("TokenSymbol", tokenSymbol); + B.putString("ContractAddress", contractAddress); + B.putString("validationNodeDescription", validationNodeDescription); + if (dtPersonalization != null) B.putLong("dtPersonalization", dtPersonalization.getTime()); + B.putInt("RemainingSignatures", remainingSignatures); + B.putInt("MaxSignatures", maxSignatures); + B.putInt("Health", health); + if (settingsMask != null) B.putInt("settingsMask", settingsMask); + B.putInt("pauseBeforePIN2", pauseBeforePIN2); + if (signingMethod != null) B.putString("signingMethod", signingMethod.name()); + if (manufacturer != null) B.putString("Manufacturer", manufacturer.name()); + if (encryptionMode != null) B.putString("EncryptionMode", encryptionMode.name()); + if (issuer != null) B.putString("Issuer", issuer.name()); + if (firmwareVersion != null) B.putString("FirmwareVersion", firmwareVersion); + B.putString("BalanceDecimal", balanceDecimal); + B.putString("BalanceDecimalAlter", balanceDecimalAlter); + B.putBoolean("ManufacturerConfirmed", manufacturerConfirmed); + B.putBoolean("CardPublicKeyValid", isCardPublicKeyValid()); + B.putByteArray("CardPublicKey", getCardPublicKey()); + + B.putString("Wallet", wallet); + B.putString("Error", error); + B.putBoolean("WalletPublicKeyValid", isWalletPublicKeyValid()); + if (pbWalletKey != null) + B.putByteArray("PublicKey", pbWalletKey); + if (pbWalletKeyRar != null) + B.putByteArray("PublicKeyRar", pbWalletKeyRar); + if (balanceConfirmed != null) B.putLong("BalanceConfirmed", balanceConfirmed); + if (balanceUnconfirmed != null) B.putLong("BalanceUnconfirmed", balanceUnconfirmed); + + if (getOfflineBalance() != null) B.putByteArray("OfflineBalance", getOfflineBalance()); + + if (getIssuerData() != null && getIssuerDataSignature() != null) { + B.putByteArray("IssuerData", getIssuerData()); + B.putByteArray("IssuerDataSignature", getIssuerDataSignature()); + B.putBoolean("NeedWriteIssuerData", getNeedWriteIssuerData()); + } + + if (mUnspentTransactions != null) { + Bundle BB = new Bundle(); + for (Integer i = 0; i < mUnspentTransactions.size(); i++) { + BB.putBundle(i.toString(), mUnspentTransactions.get(i).getAsBundle()); + } + B.putBundle("UnspentTransactions", BB); + } + if (mHeaders != null) { + Bundle BB = new Bundle(); + for (Integer i = 0; i < mHeaders.size(); i++) { + BB.putBundle(i.toString(), mHeaders.get(i).getAsBundle()); + } + B.putBundle("Headers", BB); + } + if (mHistoryTransactions != null) { + Bundle BB = new Bundle(); + for (Integer i = 0; i < mHistoryTransactions.size(); i++) { + BB.putBundle(i.toString(), mHistoryTransactions.get(i).getAsBundle()); + } + B.putBundle("HistoryTransactions", BB); + } + B.putFloat("rate", rate); + B.putFloat("rateAlter", rateAlter); + B.putString("confirmTx", GetConfirmTXCount().toString(16)); + } + + public void LoadFromBundle(Bundle B) { + UID = B.getString("UID"); + CID = B.getByteArray("CID"); + PIN = B.getString("PIN"); + PIN2 = PIN2_Mode.valueOf(B.getString("PIN2")); + status = Status.valueOf(B.getString("Status")); + wallet = B.getString("Wallet"); + error = B.getString("Error"); + blockchainID = B.getString("Blockchain"); + tokensDecimal = B.getInt("TokensDecimal", 18); + tokenSymbol = B.getString("TokenSymbol", ""); + contractAddress = B.getString("ContractAddress", ""); + if (B.containsKey("BlockchainName")) + blochchainName = B.getString("BlockchainName", ""); + validationNodeDescription = B.getString("validationNodeDescription"); + if (B.containsKey("dtPersonalization")) { + dtPersonalization = new Date(B.getLong("dtPersonalization")); + } + balanceDecimal = B.getString("BalanceDecimal"); + balanceDecimalAlter = B.getString("BalanceDecimalAlter"); + remainingSignatures = B.getInt("RemainingSignatures"); + maxSignatures = B.getInt("MaxSignatures"); + health = B.getInt("health"); + if (B.containsKey("settingsMask")) settingsMask = B.getInt("settingsMask"); + pauseBeforePIN2 = B.getInt("pauseBeforePIN2"); + if (B.containsKey("signingMethod")) + signingMethod = SigningMethod.valueOf(B.getString("signingMethod")); + if (B.containsKey("Manufacturer")) + manufacturer = Manufacturer.valueOf(B.getString("Manufacturer")); + manufacturerConfirmed = B.getBoolean("ManufacturerConfirmed"); + if (B.containsKey("EncryptionMode")) + encryptionMode = EncryptionMode.valueOf(B.getString("EncryptionMode")); + else + encryptionMode = null; + + if (B.containsKey("Issuer")) issuer = Issuer.valueOf(B.getString("Issuer")); + if (B.containsKey("FirmwareVersion")) firmwareVersion = B.getString("FirmwareVersion"); + + cardPublicKeyValid = B.getBoolean("CardPublicKeyValid"); + if (B.containsKey("CardPublicKey")) setCardPublicKey(B.getByteArray("CardPublicKey")); + + if (B.containsKey("BalanceConfirmed")) balanceConfirmed = B.getLong("BalanceConfirmed"); + else balanceConfirmed = null; + if (B.containsKey("BalanceUnconfirmed")) + balanceUnconfirmed = B.getLong("BalanceUnconfirmed"); + else balanceUnconfirmed = null; + + if (B.containsKey("OfflineBalance")) setOfflineBalance(B.getByteArray("OfflineBalance")); + else clearOfflineBalance(); + + if (B.containsKey("IssuerData")) + setIssuerData(B.getByteArray("IssuerData"), B.getByteArray("IssuerDataSignature")); + else setIssuerData(null, null); + + if (B.containsKey("NeedWriteIssuerData")) + setNeedWriteIssuerData(B.getBoolean("NeedWriteIssuerData")); + + walletPublicKeyValid = B.getBoolean("WalletPublicKeyValid"); + if (B.containsKey("PublicKey")) { + pbWalletKey = B.getByteArray("PublicKey"); + } + if (B.containsKey("PublicKeyRar")) { + pbWalletKeyRar = B.getByteArray("PublicKeyRar"); + } + + if (B.containsKey("UnspentTransactions")) { + mUnspentTransactions = new ArrayList<>(); + Bundle BB = B.getBundle("UnspentTransactions"); + Integer i = 0; + while (BB.containsKey(i.toString())) { + UnspentTransaction t = new UnspentTransaction(); + t.LoadFromBundle(BB.getBundle(i.toString())); + mUnspentTransactions.add(t); + i++; + } + } + if (B.containsKey("Headers")) { + mHeaders = new ArrayList<>(); + Bundle BB = B.getBundle("Headers"); + Integer i = 0; + while (BB.containsKey(i.toString())) { + HeaderInfo t = new HeaderInfo(); + t.LoadFromBundle(BB.getBundle(i.toString())); + mHeaders.add(t); + i++; + } + } + if (B.containsKey("HistoryTransactions")) { + mHistoryTransactions = new ArrayList<>(); + Bundle BB = B.getBundle("HistoryTransactions"); + Integer i = 0; + while (BB.containsKey(i.toString())) { + HistoryTransaction t = new HistoryTransaction(); + t.LoadFromBundle(BB.getBundle(i.toString())); + mHistoryTransactions.add(t); + i++; + } + } + if (B.containsKey("rate")) + rate = B.getFloat("rate"); + if (B.containsKey("rateAlter")) + rateAlter = B.getFloat("rateAlter"); + if (B.containsKey("confirmTx")) + countConfirmTX = new BigInteger(B.getString("confirmTx"), 16); + } + + public enum EncryptionMode { + None((byte) 0x0), Fast((byte) 0x1), Strong((byte) 0x2); + + private byte P; + + EncryptionMode(byte P) { + this.P = P; + } + + public int getP() { + return P; + } + } + + public EncryptionMode encryptionMode = EncryptionMode.None; +} diff --git a/app/src/main/java/com/tangem/wallet/TokenEngine.java b/app/src/main/java/com/tangem/wallet/TokenEngine.java new file mode 100644 index 0000000000..d9975bd2db --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/TokenEngine.java @@ -0,0 +1,436 @@ +package com.tangem.wallet; + +import android.net.Uri; +import android.util.Log; + +import com.google.common.base.Strings; +import com.tangem.cardReader.CardProtocol; +import com.tangem.cardReader.TLV; + +import org.bitcoinj.core.ECKey; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.math.RoundingMode; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.text.DecimalFormat; +import java.util.Arrays; +import java.util.Date; + +import static com.tangem.wallet.FormatUtil.GetDecimalFormat; + +/** + * Created by Ilia on 20.03.2018. + */ + +public class TokenEngine extends CoinEngine{ + public String GetNextNode(Tangem_Card mCard) + { + return "abc1.hsmiths.com"; + } + public int GetNextNodePort(Tangem_Card mCard) + { + return 60001; + } + public String GetNode(Tangem_Card mCard) + { + return "abc1.hsmiths.com"; + } + public int GetNodePort(Tangem_Card mCard) + { + return 60001; + } + public void SwitchNode(Tangem_Card mCard) + { + } + public boolean AwaitingConfirmation(Tangem_Card card) + { + return false; + } + + public boolean InOutPutVisible() + { + return false; + } + + public String GetBalanceCurrency(Tangem_Card card) + { + String currency = card.getTokenSymbol(); + if(Strings.isNullOrEmpty(currency)) + return "NoN"; + return currency; + } + + public String GetFeeCurrency() + { + return "Gwei"; + } + + BigDecimal convertToEth(String value) + { + BigInteger m = new BigInteger(value, 10); + BigDecimal n = new BigDecimal(m); + BigDecimal d = n.divide(new BigDecimal("1000000000000000000")); + d = d.setScale(8, RoundingMode.DOWN); + return d; + } + + + public int GetTokenDecimals(Tangem_Card card) + { + return card.getTokensDecimal(); + } + + public String GetContractAddress(Tangem_Card card) + { + return card.getContractAddress(); + } + public boolean IsNeedCheckNode() + { + return false; + } + + public boolean ValdateAddress(String address, Tangem_Card card) { + if (address == null || address.isEmpty()) { + return false; + } + + if(!address.startsWith("0x")&&!address.startsWith("0X")) + { + return false; + } + + if(address.length()!=42) + { + return false; + } + + return true; + } + + public String GetBalanceAlterValue(Tangem_Card mCard) + { + String dec = mCard.getDecimalBalanceAlter(); + BigDecimal d = convertToEth(dec); + String s = d.toString(); + + String pattern = "#0.000"; // If you like 4 zeros + DecimalFormat myFormatter = new DecimalFormat(pattern); + String output = myFormatter.format(d); + return output; + } + + public String GetBalanceValue(Tangem_Card mCard) + { + if(!HasBalanceInfo(mCard)) + return "-- -- -- " + GetBalanceCurrency(mCard); + + String dec = mCard.getDecimalBalance(); + BigDecimal d = new BigDecimal(dec); + BigDecimal p = new BigDecimal(10); + p = p.pow(GetTokenDecimals(mCard)); + BigDecimal l = d.divide(p); + + String pattern = "#0.000"; // If you like 4 zeros + DecimalFormat myFormatter = new DecimalFormat(pattern); + String output = myFormatter.format(l); + return output; + } + + public boolean CheckAmount(Tangem_Card card, String amount) throws Exception + { + DecimalFormat decimalFormat = GetDecimalFormat(); + BigDecimal amountValue = (BigDecimal) decimalFormat.parse(amount); //new BigDecimal(strAmount); + BigDecimal maxValue = new BigDecimal(GetBalanceValue(card)); + if(amountValue.compareTo(maxValue) > 0 ) + { + return false; + } + + return true; + } + + public Long GetBalanceLong(Tangem_Card mCard) + { + return mCard.getBalance(); + } + + public boolean IsBalanceAlterNotZero(Tangem_Card card) + { + String balance = card.getDecimalBalanceAlter(); + if(balance == null || balance == "") + return false; + + BigDecimal bi = new BigDecimal(balance); + + if (BigDecimal.ZERO.compareTo(bi) == 0) + return false; + + return true; + } + + public boolean IsBalanceNotZero(Tangem_Card card) + { + String balance = card.getDecimalBalance(); + if(balance == null || balance == "") + return false; + + BigDecimal bi = new BigDecimal(balance); + + if (BigDecimal.ZERO.compareTo(bi) == 0) + return false; + + return true; + } + + public boolean HasBalanceInfo(Tangem_Card card) + { + String balance = card.getDecimalBalance(); + if(balance == null || balance == "") + return false; + + String balanceEx = card.getDecimalBalanceAlter(); + if(balanceEx == null || balanceEx == "") + return false; + return true; + } + + @Override + public String GetBalanceEquivalent(Tangem_Card mCard) { + if(!HasBalanceInfo(mCard)){ + return "-- -- -- "; + } + String dec = mCard.getDecimalBalance(); + BigDecimal d = convertToEth(dec); + return EthEngine.getAmountEquivalentDescriptionETH(d, mCard.getRate()); + } + + @Override + public String GetBalance(Tangem_Card mCard) { + if(!HasBalanceInfo(mCard)){ + return "-- -- -- " + GetBalanceCurrency(mCard); + } + + String output = GetBalanceValue(mCard); + String s = output + " " + GetBalanceCurrency(mCard); + return s; + } + + + + + public String GetBalanceWithAlter(Tangem_Card mCard) + { + //return GetBalance(mCard) + "\n(" + GetBalanceAlterValue(mCard) + " ETH)"; + return " " + GetBalance(mCard) + "
+ " + GetBalanceAlterValue(mCard) + " ETH for gas"; + } + + public String calculateAddress(Tangem_Card mCard, byte[] pkUncompressed) throws NoSuchProviderException, NoSuchAlgorithmException { + Keccak256 kec = new Keccak256(); + int lenPk = pkUncompressed.length; + if (lenPk < 2) { + throw new IllegalArgumentException("Uncompress public key length is invald"); + } + byte[] cleanKey = new byte[lenPk - 1]; + for (int i = 0; i < cleanKey.length; ++i) { + cleanKey[i] = pkUncompressed[i + 1]; + } + byte[] r = kec.digest(cleanKey); + + byte[] address = new byte[20]; + for (int i = 0; i < 20; ++i) { + address[i] = r[i + 12]; + } + + return String.format("0x%s", BTCUtils.toHex(address)); + } + + @Override + public String ConvertByteArrayToAmount(Tangem_Card mCard, byte[] bytes) throws Exception { + throw new Exception("Not implemented"); + } + + @Override + public byte[] ConvertAmountToByteArray(Tangem_Card mCard, String amount) throws Exception { + throw new Exception("Not implemented"); + } + + @Override + public String GetAmountDescription(Tangem_Card mCard, String amount) throws Exception { + throw new Exception("Not implemented"); + } + + + public String GetAmountEqualentDescriptor(Tangem_Card mCard, String value) + { + BigDecimal d = new BigDecimal(value); + return EthEngine.getAmountEquivalentDescriptionETH(d, mCard.getRate()); + } + + public String GetFeeEqualentDescriptor(Tangem_Card mCard, String value) + { + BigDecimal d = new BigDecimal(value); + return EthEngine.getAmountEquivalentDescriptionETH(d, mCard.getRateAlter()); + } + + public Uri getShareWalletURIExplorer(Tangem_Card mCard) + { + return Uri.parse("https://etherscan.io/token/"+GetContractAddress(mCard)+"?a=" + mCard.getWallet()); + } + + public Uri getShareWalletURI(Tangem_Card mCard) + { + return Uri.parse("" + mCard.getWallet()); + } + + public boolean CheckUnspentTransaction(Tangem_Card mCard) + { + return true; + } + + public boolean CheckAmountValie(Tangem_Card mCard, String amountValue, String feeValue, Long minFeeInInternalUnits) + { + Long fee = null; + BigDecimal amount = null; + try { + amount = new BigDecimal(GetBalanceAlterValue(mCard));//mCard.InternalUnitsFromString(amountValue); + fee = mCard.InternalUnitsFromString(feeValue); + } catch (Exception e) { + e.printStackTrace(); + return false; + } + + if(fee == null || amount == null) + return false; + + if(fee == 0 || amount.compareTo(BigDecimal.ZERO) == 0) + return false; + + + if(fee < minFeeInInternalUnits) + return false; + + + BigDecimal tmpFee = new BigDecimal(feeValue); + BigDecimal tmpAmount = amount; + tmpAmount = tmpAmount.multiply(new BigDecimal("1000000000")); + + if (tmpFee.compareTo(tmpAmount) > 0) + return false; + + return true; + } + + public String EvaluteFeeEquivalent(Tangem_Card mCard, String fee) + { + BigDecimal gweFee = new BigDecimal(fee); + gweFee = gweFee.divide(new BigDecimal("1000000000")); + gweFee = gweFee.setScale(18, RoundingMode.DOWN); + return GetFeeEqualentDescriptor(mCard, gweFee.toString()); + } + + public byte[] Sign(String feeValue, String amountValue, String toValue, Tangem_Card mCard, CardProtocol protocol) throws Exception { + + BigInteger nonceValue = mCard.GetConfirmTXCount(); + byte[] pbKey = mCard.getWalletPublicKey(); + boolean flag = (mCard.getSigningMethod()== Tangem_Card.SigningMethod.Sign_Hash_Validated_By_Issuer); + Issuer issuer = mCard.getIssuer(); + + + BigInteger fee = new BigInteger(feeValue, 10); + + BigDecimal amountDecValue = new BigDecimal(amountValue); + + int d = GetTokenDecimals(mCard); + BigDecimal amountDec = new BigDecimal("10"); + amountDec = amountDec.pow(d); + amountDec = amountDecValue.multiply(amountDec); + + //amountDec = amountDec.multiply(new BigDecimal("1000000000")); + + + BigInteger amount = amountDec.toBigInteger(); //new BigInteger(amountValue, 10); + + + + + //amount = amount.subtract(fee); + + BigInteger nonce = nonceValue; + BigInteger gasPrice = fee.divide(BigInteger.valueOf(21000)); + BigInteger gasLimit = BigInteger.valueOf(60000); + Integer chainId = ETH_Transaction.ChainEnum.Mainnet.getValue(); + BigInteger amountZero = BigInteger.ZERO; + + Long multiplicator = 1000000000L; + + gasPrice = gasPrice.multiply(BigInteger.valueOf(multiplicator)); + + String to = toValue; + + if (to.startsWith("0x") || to.startsWith("0X")) { + to = to.substring(2); + } + + String contractAddress = GetContractAddress(mCard); + + if (contractAddress.startsWith("0x") || contractAddress.startsWith("0X")) { + contractAddress = contractAddress.substring(2); + } + + String amountLeadZero = amount.toString(16); + if (amountLeadZero.startsWith("0x") || amountLeadZero.startsWith("0X")) { + amountLeadZero = amountLeadZero.substring(2); + } + + while(amountLeadZero.length() < 64) + { + amountLeadZero = "0" + amountLeadZero; + } + + String cmd = "a9059cbb000000000000000000000000"+to+amountLeadZero; //TODO only for BAT + + + byte[] data = BTCUtils.fromHex(cmd); + ETH_Transaction tx = ETH_Transaction.create(contractAddress, amountZero, nonce, gasPrice, gasLimit, chainId, data); + + byte[][] hashesForSign = new byte[1][]; + byte[] for_hash = tx.getRawHash(); + hashesForSign[0] = for_hash; + + byte[] signFromCard = null; + try { + signFromCard = protocol.run_SignHashes(PINStorage.getPIN2(), hashesForSign, flag, null, issuer).getTLV(TLV.Tag.TAG_Signature).Value; + // TODO slice signFromCard to hashes.length parts + } catch (Exception ex) { + Log.e("ETH", ex.getMessage()); + return null; + } + + LastSignStorage.setLastSignDate(mCard.getWallet(), new Date()); + + BigInteger r = new BigInteger(1, Arrays.copyOfRange(signFromCard, 0, 32)); + BigInteger s = new BigInteger(1, Arrays.copyOfRange(signFromCard, 32, 64)); + s = CryptoUtil.toCanonicalised(s); + + boolean f = ECKey.verify(for_hash, new ECKey.ECDSASignature(r, s), pbKey); + + if(!f) + { + Log.e("ETH-CHECK", "Sign Failed."); + } + + tx.signature = new ECDSASignature_ETH(r, s); + int v = tx.BruteRecoveryID2(tx.signature, for_hash, pbKey); + if (v != 27 && v != 28) { + Log.e("ETH", "invalid v"); + return null; + } + tx.signature.v = (byte) v; + Log.e("ETH_v", String.valueOf(v)); + + byte[] realTX = tx.getEncoded(); + return realTX; + } +} diff --git a/app/src/main/java/com/tangem/wallet/Transaction.java b/app/src/main/java/com/tangem/wallet/Transaction.java new file mode 100644 index 0000000000..3c755b03c0 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/Transaction.java @@ -0,0 +1,632 @@ +package com.tangem.wallet; + +/** + * Created by Ilia on 29.09.2017. + */ + +import org.spongycastle.jcajce.provider.symmetric.ARC4; + +import java.io.ByteArrayOutputStream; +import java.io.EOFException; +import java.io.IOException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Arrays; +import java.util.Stack; + +@SuppressWarnings("WeakerAccess") +public final class Transaction { + public final int version; + public final Input[] inputs; + public final Output[] outputs; + public final int lockTime; + + public Transaction(byte[] rawBytes) throws BitcoinException { + if (rawBytes == null) { + throw new BitcoinException(BitcoinException.ERR_NO_INPUT, "empty input"); + } + BitcoinInputStream bais = null; + try { + bais = new BitcoinInputStream(rawBytes); + version = bais.readInt32(); + if (version != 1 && version != 2 && version != 3) { + throw new BitcoinException(BitcoinException.ERR_UNSUPPORTED, "Unsupported TX version", version); + } + + + int inputsCount = 0; + int first = bais.readByte(); + if(first == 0) + { + int skip = bais.readByte(); + inputsCount = bais.readByte(); + } + else + { + inputsCount = first; + } + //int inputsCount = (int) bais.readVarInt(); TODO: + inputs = new Input[inputsCount]; + for (int i = 0; i < inputsCount; i++) { + OutPoint outPoint = new OutPoint(BTCUtils.reverse(bais.readChars(32)), bais.readInt32()); + byte[] script = bais.readChars((int) bais.readVarInt()); + int sequence = bais.readInt32(); + inputs[i] = new Input(outPoint, new Script(script), sequence); + } + int outputsCount = (int) bais.readVarInt(); + outputs = new Output[outputsCount]; + for (int i = 0; i < outputsCount; i++) { + long value = bais.readInt64(); + long scriptSize = bais.readVarInt(); + if (scriptSize < 0 || scriptSize > 10_000_000) { + throw new BitcoinException(BitcoinException.ERR_BAD_FORMAT, "Script size for output " + i + + " is strange (" + scriptSize + " bytes)."); + } + byte[] script = bais.readChars((int) scriptSize); + outputs[i] = new Output(value, new Script(script)); + } + lockTime = bais.readInt32(); + } catch (EOFException e) { + throw new BitcoinException(BitcoinException.ERR_BAD_FORMAT, "TX incomplete"); + } catch (IOException e) { + throw new IllegalArgumentException("Unable to read TX"); + } catch (Error e) { + throw new IllegalArgumentException("Unable to read TX: " + e); + } finally { + if (bais != null) { + try { + bais.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + } + + public Transaction(Input[] inputs, Output[] outputs, int lockTime) { + this.version = 1; + this.inputs = inputs; + this.outputs = outputs; + this.lockTime = lockTime; + } + + public byte[] getBytes() { + BitcoinOutputStream baos = new BitcoinOutputStream(); + try { + baos.writeInt32(version); + baos.writeVarInt(inputs.length); + for (Input input : inputs) { + baos.write(BTCUtils.reverse(input.outPoint.hash)); + baos.writeInt32(input.outPoint.index); + int scriptLen = input.script == null ? 0 : input.script.bytes.length; + baos.writeVarInt(scriptLen); + if (scriptLen > 0) { + baos.write(input.script.bytes); + } + baos.writeInt32(input.sequence); + } + baos.writeVarInt(outputs.length); + for (Output output : outputs) { + baos.writeInt64(output.value); + int scriptLen = output.script == null ? 0 : output.script.bytes.length; + baos.writeVarInt(scriptLen); + if (scriptLen > 0) { + baos.write(output.script.bytes); + } + } + baos.writeInt32(lockTime); + } catch (IOException e) { + e.printStackTrace(); + } finally { + try { + baos.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + return baos.toByteArray(); + + } + + @Override + public String toString() { + return "{" + + "\n\"inputs\":\n" + printAsJsonArray(inputs) + + ",\n\"outputs\":\n" + printAsJsonArray(outputs) + + ",\n\"lockTime\":\"" + lockTime + "\"}\n"; + } + + private String printAsJsonArray(Object[] a) { + if (a == null) { + return "null"; + } + if (a.length == 0) { + return "[]"; + } + int iMax = a.length - 1; + StringBuilder sb = new StringBuilder(); + sb.append('['); + for (int i = 0; ; i++) { + sb.append(String.valueOf(a[i])); + if (i == iMax) + return sb.append(']').toString(); + sb.append(",\n"); + } + } + + public static class Input { + public final OutPoint outPoint; + public final Script script; + public final int sequence; + + public Input(OutPoint outPoint, Script script, int sequence) { + this.outPoint = outPoint; + this.script = script; + this.sequence = sequence; + } + + @Override + public String toString() { + return "{\n\"outPoint\":" + outPoint + ",\n\"script\":\"" + script + "\",\n\"sequence\":\"" + Integer.toHexString(sequence) + "\"\n}\n"; + } + } + + public static class OutPoint { + public final byte[] hash;//32-byte hash of the transaction from which we want to redeem an output + public final int index;//Four-byte field denoting the output index we want to redeem from the transaction with the above hash (output number 2 = output index 1) + + public OutPoint(byte[] hash, int index) { + this.hash = hash; + this.index = index; + } + + @Override + public String toString() { + return "{" + "\"hash\":\"" + BTCUtils.toHex(hash) + "\", \"index\":\"" + index + "\"}"; + } + } + + public static class Output { + public final long value; + public final Script script; + + public Output(long value, Script script) { + this.value = value; + this.script = script; + } + + @Override + public String toString() { + return "{\n\"value\":\"" + value * 1e-8 + "\",\"script\":\"" + script + "\"\n}"; + } + } + + public static final class Script { + + public static class ScriptInvalidException extends Exception { + public ScriptInvalidException() { + } + + public ScriptInvalidException(String s) { + super(s); + } + } + + public static final byte OP_FALSE = 0; + public static final byte OP_TRUE = 0x51; + public static final byte OP_PUSHDATA1 = 0x4c; + public static final byte OP_PUSHDATA2 = 0x4d; + public static final byte OP_PUSHDATA4 = 0x4e; + public static final byte OP_DUP = 0x76;//Duplicates the top stack item. + public static final byte OP_DROP = 0x75; + public static final byte OP_HASH160 = (byte) 0xA9;//The input is hashed twice: first with SHA-256 and then with RIPEMD-160. + public static final byte OP_VERIFY = 0x69;//Marks transaction as invalid if top stack value is not true. True is removed, but false is not. + public static final byte OP_EQUAL = (byte) 0x87;//Returns 1 if the inputs are exactly equal, 0 otherwise. + public static final byte OP_EQUALVERIFY = (byte) 0x88;//Same as OP_EQUAL, but runs OP_VERIFY afterward. + public static final byte OP_CHECKSIG = (byte) 0xAC;//The entire transaction's outputs, inputs, and script (from the most recently-executed OP_CODESEPARATOR to the end) are hashed. The signature used by OP_CHECKSIG must be a valid signature for this hash and public key. If it is, 1 is returned, 0 otherwise. + public static final byte OP_CHECKSIGVERIFY = (byte) 0xAD; + public static final byte OP_NOP = 0x61; + + public static final byte SIGHASH_ALL = 1; + + public final byte[] bytes; + + public Script(byte[] rawBytes) { + bytes = rawBytes; + } + + public Script(byte[] data1, byte[] data2) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(data1.length + data2.length + 2); + try { + writeBytes(data1, baos); + writeBytes(data2, baos); + baos.close(); + } catch (IOException e) { + throw new RuntimeException(e); + } + bytes = baos.toByteArray(); + } + + private static void writeBytes(byte[] data, ByteArrayOutputStream baos) throws IOException { + if (data.length < OP_PUSHDATA1) { + baos.write(data.length); + } else if (data.length < 0xff) { + baos.write(OP_PUSHDATA1); + baos.write(data.length); + } else if (data.length < 0xffff) { + baos.write(OP_PUSHDATA2); + baos.write(data.length & 0xff); + baos.write((data.length >> 8) & 0xff); + } else { + baos.write(OP_PUSHDATA4); + baos.write(data.length & 0xff); + baos.write((data.length >> 8) & 0xff); + baos.write((data.length >> 16) & 0xff); + baos.write((data.length >>> 24) & 0xff); + } + baos.write(data); + } + + public void run(Stack stack) throws ScriptInvalidException { + run(0, null, stack); + } + + public void run(int inputIndex, Transaction tx, Stack stack) throws ScriptInvalidException { + for (int pos = 0; pos < bytes.length; pos++) { + switch (bytes[pos]) { + case OP_NOP: + break; + case OP_DROP: + if (stack.isEmpty()) { + throw new IllegalArgumentException("stack empty on OP_DROP"); + } + stack.pop(); + break; + case OP_DUP: + if (stack.isEmpty()) { + throw new IllegalArgumentException("stack empty on OP_DUP"); + } + stack.push(stack.peek()); + break; + case OP_HASH160: + if (stack.isEmpty()) { + throw new IllegalArgumentException("stack empty on OP_HASH160"); + } + stack.push(CryptoUtil.sha256ripemd160(stack.pop())); + break; + case OP_EQUAL: + case OP_EQUALVERIFY: + if (stack.size() < 2) { + throw new IllegalArgumentException("not enough elements to perform OP_EQUAL"); + } + stack.push(new byte[]{(byte) (Arrays.equals(stack.pop(), stack.pop()) ? 1 : 0)}); + if (bytes[pos] == OP_EQUALVERIFY) { + if (verifyFails(stack)) { + throw new ScriptInvalidException("wrong address"); + } + } + break; + case OP_VERIFY: + if (verifyFails(stack)) { + throw new ScriptInvalidException(); + } + break; + case OP_CHECKSIG: + case OP_CHECKSIGVERIFY: + byte[] publicKey = stack.pop(); + byte[] signatureAndHashType = stack.pop(); + if (signatureAndHashType[signatureAndHashType.length - 1] != SIGHASH_ALL) { + throw new IllegalArgumentException("I cannot check this sig type: " + signatureAndHashType[signatureAndHashType.length - 1]); + } + byte[] signature = new byte[signatureAndHashType.length - 1]; + System.arraycopy(signatureAndHashType, 0, signature, 0, signature.length); + byte[] hash = hashTransaction(inputIndex, bytes, tx); + //boolean valid = BTCUtils.verify(publicKey, signature, hash); + if (bytes[pos] == OP_CHECKSIG) { + stack.push(new byte[]{(byte) (1)}); + } else { + if (verifyFails(stack)) { + throw new ScriptInvalidException("Bad signature"); + } + if (!stack.empty()) { + throw new ScriptInvalidException("Bad signature - superfluous scriptSig operations"); + } + } + break; + case OP_FALSE: + stack.push(new byte[]{0}); + break; + case OP_TRUE: + stack.push(new byte[]{1}); + break; + default: + int op = bytes[pos] & 0xff; + int len; + if (op < OP_PUSHDATA1) { + len = op; + byte[] data = new byte[len]; + System.arraycopy(bytes, pos + 1, data, 0, len); + stack.push(data); + pos += data.length; + } else if (op == OP_PUSHDATA1) { + len = bytes[pos + 1] & 0xff; + byte[] data = new byte[len]; + System.arraycopy(bytes, pos + 1, data, 0, len); + stack.push(data); + pos += 1 + data.length; + } else { + throw new IllegalArgumentException("I cannot read this data: " + Integer.toHexString(bytes[pos])); + } + break; + } + } + } + + public static byte[] hashTransaction(int inputIndex, byte[] subscript, Transaction tx) { + Input[] unsignedInputs = new Input[tx.inputs.length]; + for (int i = 0; i < tx.inputs.length; i++) { + Input txInput = tx.inputs[i]; + if (i == inputIndex) { + unsignedInputs[i] = new Input(txInput.outPoint, new Script(subscript), txInput.sequence); + } else { + unsignedInputs[i] = new Input(txInput.outPoint, new Script(new byte[0]), txInput.sequence); + } + } + Transaction unsignedTransaction = new Transaction(unsignedInputs, tx.outputs, tx.lockTime); + return hashTransactionForSigning(unsignedTransaction); + } + + public static byte[] hashTransactionForSigning(Transaction unsignedTransaction) { + byte[] txUnsignedBytes = unsignedTransaction.getBytes(); + BitcoinOutputStream baos = new BitcoinOutputStream(); + try { + baos.write(txUnsignedBytes); + baos.writeInt32(Script.SIGHASH_ALL); + baos.close(); + } catch (Exception e) { + throw new RuntimeException(e); + } + return CryptoUtil.doubleSha256(baos.toByteArray()); + } + + public static boolean verifyFails(Stack stack) { + byte[] input; + boolean valid; + input = stack.pop(); + if (input.length == 0 || (input.length == 1 && input[0] == OP_FALSE)) { + //false + stack.push(new byte[]{OP_FALSE}); + valid = false; + } else { + //true + valid = true; + } + return !valid; + } + + + @Override + public String toString() { + return convertBytesToReadableString(bytes); + } + + //converts something like "OP_DUP OP_HASH160 ba507bae8f1643d2556000ca26b9301b9069dc6b OP_EQUALVERIFY OP_CHECKSIG" into bytes + public static byte[] convertReadableStringToBytes(String readableString) { + String[] tokens = readableString.trim().split("\\s+"); + ByteArrayOutputStream os = new ByteArrayOutputStream(); + for (String token : tokens) { + switch (token) { + case "OP_NOP": + os.write(OP_NOP); + break; + case "OP_DROP": + os.write(OP_DROP); + break; + case "OP_DUP": + os.write(OP_DUP); + break; + case "OP_HASH160": + os.write(OP_HASH160); + break; + case "OP_EQUAL": + os.write(OP_EQUAL); + break; + case "OP_EQUALVERIFY": + os.write(OP_EQUALVERIFY); + break; + case "OP_VERIFY": + os.write(OP_VERIFY); + break; + case "OP_CHECKSIG": + os.write(OP_CHECKSIG); + break; + case "OP_CHECKSIGVERIFY": + os.write(OP_CHECKSIGVERIFY); + break; + case "OP_FALSE": + os.write(OP_FALSE); + break; + case "OP_TRUE": + os.write(OP_TRUE); + break; + default: + if (token.startsWith("OP_")) { + throw new IllegalArgumentException("I don't know this operation: " + token); + } + byte[] data = BTCUtils.fromHex(token); + if (data == null) { + throw new IllegalArgumentException("I don't know what's this: " + token); + } + if (data.length < OP_PUSHDATA1) { + os.write(data.length); + try { + os.write(data); + } catch (IOException e) { + throw new RuntimeException("ByteArrayOutputStream behaves weird: " + e); + } + } else if (data.length <= 255) { + os.write(OP_PUSHDATA1); + os.write(data.length); + try { + os.write(data); + } catch (IOException e) { + throw new RuntimeException("ByteArrayOutputStream behaves weird: " + e); + } + } else { + throw new IllegalArgumentException("OP_PUSHDATA2 & OP_PUSHDATA4 are not supported"); + } + break; + } + } + try { + os.close(); + } catch (IOException e) { + e.printStackTrace(); + } + return os.toByteArray(); + } + + public static String convertBytesToReadableString(byte[] bytes) { + StringBuilder sb = new StringBuilder(); + for (int pos = 0; pos < bytes.length; pos++) { + if (sb.length() > 0) { + sb.append(' '); + } + switch (bytes[pos]) { + case OP_NOP: + sb.append("OP_NOP"); + break; + case OP_DROP: + sb.append("OP_DROP"); + break; + case OP_DUP: + sb.append("OP_DUP"); + break; + case OP_HASH160: + sb.append("OP_HASH160"); + break; + case OP_EQUAL: + sb.append("OP_EQUAL"); + break; + case OP_EQUALVERIFY: + sb.append("OP_EQUALVERIFY"); + break; + case OP_VERIFY: + sb.append("OP_VERIFY"); + break; + case OP_CHECKSIG: + sb.append("OP_CHECKSIG"); + break; + case OP_CHECKSIGVERIFY: + sb.append("OP_CHECKSIGVERIFY"); + break; + case OP_FALSE: + sb.append("OP_FALSE"); + break; + case OP_TRUE: + sb.append("OP_TRUE"); + break; + default: + int op = bytes[pos] & 0xff; + int len; + if (op < OP_PUSHDATA1) { + len = op; + byte[] data = new byte[len]; + System.arraycopy(bytes, pos + 1, data, 0, len); + sb.append(BTCUtils.toHex(data)); + pos += data.length; + } else if (op == OP_PUSHDATA1) { + len = bytes[pos + 1] & 0xff; + byte[] data = new byte[len]; + System.arraycopy(bytes, pos + 1, data, 0, len);//FIXME I suspect there is off by one error... + sb.append(BTCUtils.toHex(data)); + pos += 1 + data.length; + } else { + throw new IllegalArgumentException("I cannot read this data: " + Integer.toHexString(bytes[pos]) + " at " + pos); + } + break; + } + } + return sb.toString(); + } + + @Override + public boolean equals(Object o) { + return this == o || !(o == null || getClass() != o.getClass()) && Arrays.equals(bytes, ((Script) o).bytes); + } + + @Override + public int hashCode() { + return Arrays.hashCode(bytes); + } + + public static Script buildOutput(String address) throws BitcoinException { + //noinspection TryWithIdenticalCatches + byte[] addressWithCheckSumAndNetworkCode = Base58.decodeBase58(address); + if (addressWithCheckSumAndNetworkCode[0] == 0 || addressWithCheckSumAndNetworkCode[0] == 111) { + return buildOutputP2H(address); + } + + if(addressWithCheckSumAndNetworkCode[0] == 5 || addressWithCheckSumAndNetworkCode[0] == (byte)0xc4) { + return buildOutputP2SH(address); + } + + throw new BitcoinException(BitcoinException.ERR_UNSUPPORTED, "Unknown address type", address); + } + public static Script buildOutputP2SH(String address) throws BitcoinException { + try { + byte[] addressWithCheckSumAndNetworkCode = Base58.decodeBase58(address); + if (addressWithCheckSumAndNetworkCode[0] != 5 && addressWithCheckSumAndNetworkCode[0] != (byte)0xc4) { + throw new BitcoinException(BitcoinException.ERR_UNSUPPORTED, "Unknown address type", address); + } + + byte[] bareAddress = new byte[20]; + System.arraycopy(addressWithCheckSumAndNetworkCode, 1, bareAddress, 0, bareAddress.length); + + ByteArrayOutputStream buf = new ByteArrayOutputStream(23); + buf.write(OP_HASH160); + writeBytes(bareAddress, buf); + buf.write(OP_EQUAL); + return new Script(buf.toByteArray()); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + + public static Script buildOutputP2H(String address) throws BitcoinException { + //noinspection TryWithIdenticalCatches + try { + byte[] addressWithCheckSumAndNetworkCode = Base58.decodeBase58(address); + if (addressWithCheckSumAndNetworkCode[0] != 0 && addressWithCheckSumAndNetworkCode[0] != 111) { + throw new BitcoinException(BitcoinException.ERR_UNSUPPORTED, "Unknown address type", address); + } + + byte[] bareAddress = new byte[20]; + System.arraycopy(addressWithCheckSumAndNetworkCode, 1, bareAddress, 0, bareAddress.length); + + MessageDigest digestSha = MessageDigest.getInstance("SHA-256"); + digestSha.update(addressWithCheckSumAndNetworkCode, 0, addressWithCheckSumAndNetworkCode.length - 4); + + byte[] calculatedDigest = digestSha.digest(digestSha.digest()); + for (int i = 0; i < 4; i++) { + if (calculatedDigest[i] != addressWithCheckSumAndNetworkCode[addressWithCheckSumAndNetworkCode.length - 4 + i]) { + throw new BitcoinException(BitcoinException.ERR_BAD_FORMAT, "Bad address", address); + } + } + + ByteArrayOutputStream buf = new ByteArrayOutputStream(25); + buf.write(OP_DUP); + buf.write(OP_HASH160); + writeBytes(bareAddress, buf); + buf.write(OP_EQUALVERIFY); + buf.write(OP_CHECKSIG); + return new Script(buf.toByteArray()); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } +} diff --git a/app/src/main/java/com/tangem/wallet/UnspentOutputInfo.java b/app/src/main/java/com/tangem/wallet/UnspentOutputInfo.java new file mode 100644 index 0000000000..a4462b69d6 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/UnspentOutputInfo.java @@ -0,0 +1,28 @@ +package com.tangem.wallet; + +/** + * Created by Ilia on 29.09.2017. + */ + +@SuppressWarnings("WeakerAccess") +public class UnspentOutputInfo { + public final byte[] txHash; + public final Transaction.Script script; + public final long value; + public final int outputIndex; + public final long confirmations; + public String txHashForBuild; + public byte[] scriptForBuild; + public byte[] bodyDoubleHash; + public byte[] bodyHash; + + public UnspentOutputInfo(byte[] txHash, Transaction.Script script, long value, int outputIndex, long confirmations, String hashForBuild, byte[] sign) { + this.txHash = txHash; + this.script = script; + this.value = value; + this.outputIndex = outputIndex; + this.confirmations = confirmations; + this.txHashForBuild = hashForBuild; + this.scriptForBuild = sign; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/wallet/VerifyCardActivity.java b/app/src/main/java/com/tangem/wallet/VerifyCardActivity.java new file mode 100644 index 0000000000..30a8153057 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/VerifyCardActivity.java @@ -0,0 +1,28 @@ +package com.tangem.wallet; + +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; +import android.support.v7.app.AppCompatActivity; + + +public class VerifyCardActivity extends AppCompatActivity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_verify_card); + + MainActivity.commonInit(getApplicationContext()); + } + + @Override + public void onBackPressed() { + //super.onBackPressed(); + VerifyCardActivityFragment verifyCardActivityFragment= (VerifyCardActivityFragment) getSupportFragmentManager().findFragmentById(R.id.verify_card_fragment); + Intent data= verifyCardActivityFragment.prepareResultIntent(); + data.putExtra("modification", "update"); + setResult(Activity.RESULT_OK, data); + finish(); + } +} diff --git a/app/src/main/java/com/tangem/wallet/VerifyCardActivityFragment.java b/app/src/main/java/com/tangem/wallet/VerifyCardActivityFragment.java new file mode 100644 index 0000000000..53f64d5fa1 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/VerifyCardActivityFragment.java @@ -0,0 +1,360 @@ +package com.tangem.wallet; + +import android.app.Activity; +import android.content.Intent; +import android.nfc.NfcAdapter; +import android.nfc.Tag; +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.support.v4.widget.SwipeRefreshLayout; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; +import android.widget.Toast; + +import com.tangem.cardReader.NfcManager; + +import java.io.IOException; +import java.util.Timer; +import java.util.TimerTask; + +public class VerifyCardActivityFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener, NfcAdapter.ReaderCallback { + + Tangem_Card mCard; + TextView tvCardID, tvManufacturer, tvRegistrationDate, tvCardIdentity, tvLastSigned, tvRemainingSignatures, tvReusable, tvOk, tvError, tvMessage, + tvIssuer, tvIssuerData, tvFeatures, tvBlockchain, tvSignedTx, tvSigningMethod, tvFirmware, tvWalletIdentity, tvWallet; + ImageView ivBlockchain, ivPIN, ivPIN2orSecurityDelay, ivDeveloperVersion; + SwipeRefreshLayout mSwipeRefreshLayout; + private NfcManager mNfcManager; + + public VerifyCardActivityFragment() { + + } + + public void onRefresh() { + mSwipeRefreshLayout.setRefreshing(false); + } + + @Override + public View onCreateView(final LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + + View v = inflater.inflate(R.layout.fragment_verify_card, container, false); + + mNfcManager = new NfcManager(this.getActivity(), this); + + + // SwipeRefreshLayout + mSwipeRefreshLayout = v.findViewById(R.id.swipe_container); + mSwipeRefreshLayout.setOnRefreshListener(this); + + mCard = new Tangem_Card(getActivity().getIntent().getStringExtra("UID")); + mCard.LoadFromBundle(getActivity().getIntent().getExtras().getBundle("Card")); + tvCardID = v.findViewById(R.id.tvCardID); + + tvLastSigned = v.findViewById(R.id.tvLastSigned); + tvRemainingSignatures = v.findViewById(R.id.tvRemainingSignatures); + + tvReusable = v.findViewById(R.id.tvReusable); + + tvManufacturer = v.findViewById(R.id.tvManufacturerInfo); + + tvCardIdentity = v.findViewById(R.id.tvCardIdentity); + + tvRegistrationDate = v.findViewById(R.id.tvCardRegistredDate); + + ivBlockchain = v.findViewById(R.id.imgBlockchain); + ivPIN = v.findViewById(R.id.imgPIN); + ivPIN2orSecurityDelay = v.findViewById(R.id.imgPIN2orSecurityDelay); + ivDeveloperVersion = v.findViewById(R.id.imgDeveloperVersion); + + tvError = v.findViewById(R.id.tvError); + tvMessage = v.findViewById(R.id.tvMessage); + + tvIssuer = v.findViewById(R.id.tvIssuer); + tvIssuerData = v.findViewById(R.id.tvIssuerData); + + tvFirmware = v.findViewById(R.id.tvFirmware); + tvFeatures = v.findViewById(R.id.tvFeatures); + tvBlockchain = v.findViewById(R.id.tvBlockchain); + + tvSignedTx = v.findViewById(R.id.tvSignedTx); + tvSigningMethod = v.findViewById(R.id.tvSigningMethod); + + tvOk = v.findViewById(R.id.tvOk); + if (tvOk != null) { + tvOk.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Intent data = prepareResultIntent(); + data.putExtra("modification", "update"); + getActivity().setResult(Activity.RESULT_OK, data); + getActivity().finish(); + } + }); + } + + tvWallet = v.findViewById(R.id.tvWallet); + tvWalletIdentity = v.findViewById(R.id.tvWalletIdentity); + + UpdateViews(); + +// if (NeedUpdate) { +// mSwipeRefreshLayout.setRefreshing(true); +// mSwipeRefreshLayout.postDelayed(new Runnable() { +// @Override +// public void run() { +// onRefresh(); +// } +// }, 1000); +// } + return v; + } + + void UpdateViews() { + try { + if (timerHideErrorAndMessage != null) { + timerHideErrorAndMessage.cancel(); + timerHideErrorAndMessage = null; + } + tvCardID.setText(mCard.getCIDDescription()); + + if (mCard.getError() == null || mCard.getError().isEmpty()) { + tvError.setVisibility(View.GONE); + tvError.setText(""); + } else { + tvError.setVisibility(View.VISIBLE); + tvError.setText(mCard.getError()); + } + if (mCard.getMessage() == null || mCard.getMessage().isEmpty()) { + tvMessage.setVisibility(View.GONE); + tvMessage.setText(""); + } else { + tvMessage.setVisibility(View.VISIBLE); + tvMessage.setText(mCard.getMessage()); + } + + tvManufacturer.setText(mCard.getManufacturer().getOfficialName()); + + if (mCard.isManufacturerConfirmed() && mCard.isCardPublicKeyValid()) { + tvCardIdentity.setText("Attested"); + tvCardIdentity.setTextColor(getResources().getColor(R.color.confirmed, getActivity().getTheme())); + } else { + tvCardIdentity.setText("Not confirmed"); + tvCardIdentity.setTextColor(getResources().getColor(R.color.not_confirmed, getActivity().getTheme())); + } + + tvIssuer.setText(mCard.getIssuerDescription()); + tvIssuerData.setText(mCard.getIssuerDataDescription()); + + tvRegistrationDate.setText(mCard.getPersonalizationDateTimeDescription()); + + //tvBlockchain.setText(mCard.getBlockchain().getOfficialName()); + tvBlockchain.setText(mCard.getBlockchainName()); + ivBlockchain.setImageResource(mCard.getBlockchain().getImageResource(this.getContext(), mCard.getTokenSymbol())); + + if (mCard.isReusable()) { + tvReusable.setText("Reusable"); + } else { + tvReusable.setText("One-off banknote"); + } + + tvSigningMethod.setText(mCard.getSigningMethod().getDescription()); + + if (mCard.getStatus() == Tangem_Card.Status.Loaded || mCard.getStatus() == Tangem_Card.Status.Purged) { + + tvLastSigned.setText(mCard.getLastSignedDescription()); + if (mCard.getRemainingSignatures() == 0) { + tvRemainingSignatures.setTextColor(getResources().getColor(R.color.not_confirmed, getActivity().getTheme())); + tvRemainingSignatures.setText("None"); + } else if (mCard.getRemainingSignatures() == 1) { + tvRemainingSignatures.setTextColor(getResources().getColor(R.color.not_confirmed, getActivity().getTheme())); + tvRemainingSignatures.setText("Last one!"); + } else if (mCard.getRemainingSignatures() > 1000) { + tvRemainingSignatures.setTextColor(getResources().getColor(R.color.confirmed, getActivity().getTheme())); + tvRemainingSignatures.setText("Unlimited"); + } else { + tvRemainingSignatures.setTextColor(getResources().getColor(R.color.confirmed, getActivity().getTheme())); + tvRemainingSignatures.setText(String.valueOf(mCard.getRemainingSignatures())); + } + tvSignedTx.setText(String.valueOf(mCard.getMaxSignatures() - mCard.getRemainingSignatures())); + } else { + tvLastSigned.setText(""); + tvRemainingSignatures.setText(""); + tvSignedTx.setText(""); + } + + tvFirmware.setText(mCard.getFirmwareVersion()); + + String features = ""; + + if (mCard.allowSwapPIN() && mCard.allowSwapPIN2()) { + features += "Allows change PIN1 and PIN2\n"; + } else if (mCard.allowSwapPIN()) { + features += "Allows change PIN1\n"; + } else if (mCard.allowSwapPIN2()) { + features += "Allows change PIN2\n"; + } else { + features += "Fixed PIN1 and PIN2\n"; + } + + if (mCard.needCVC()) { + features += "Requires CVC\n"; + } + + if (mCard.supportDynamicNDEF()) { + features += "Dynamic NDEF for iOS\n"; + } else if (mCard.supportNDEF()) { + features += "NDEF\n"; + } + + if (mCard.supportBlock()) { + features += "Blockable\n"; + } + + if (mCard.supportOnlyOneCommandAtTime()) { + features += "Atomic command mode"; + } + + if (features.endsWith("\n")) { + features = features.substring(0, features.length() - 1); + } + tvFeatures.setText(features); + + if (mCard.useDefaultPIN1()) { + ivPIN.setImageResource(R.drawable.unlock_pin1); + ivPIN.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Toast.makeText(getContext(), "This banknote is protected by default PIN1 code", Toast.LENGTH_LONG).show(); + } + }); + } else { + ivPIN.setImageResource(R.drawable.lock_pin1); + ivPIN.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Toast.makeText(getContext(), "This banknote is protected by user's PIN1 code", Toast.LENGTH_LONG).show(); + } + }); + } + + if (mCard.getPauseBeforePIN2() > 0 && (mCard.useDefaultPIN2() || !mCard.useSmartSecurityDelay())) { + ivPIN2orSecurityDelay.setImageResource(R.drawable.timer); + ivPIN2orSecurityDelay.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Toast.makeText(getContext(), String.format("This banknote will enforce %.0f seconds security delay for all operations requiring PIN2 code", mCard.getPauseBeforePIN2() / 1000.0), Toast.LENGTH_LONG).show(); + } + }); + + } else if (mCard.useDefaultPIN2()) { + ivPIN2orSecurityDelay.setImageResource(R.drawable.unlock_pin2); + ivPIN2orSecurityDelay.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Toast.makeText(getContext(), "This banknote is protected by default PIN2 code", Toast.LENGTH_LONG).show(); + } + }); + } else { + ivPIN2orSecurityDelay.setImageResource(R.drawable.lock_pin2); + ivPIN2orSecurityDelay.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Toast.makeText(getContext(), "This banknote is protected by user's PIN2 code", Toast.LENGTH_LONG).show(); + } + }); + } + + + if (mCard.useDevelopersFirmware()) { + ivDeveloperVersion.setImageResource(R.drawable.ic_developer_version); + ivDeveloperVersion.setVisibility(View.VISIBLE); + ivDeveloperVersion.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Toast.makeText(getContext(), "Unlocked banknote, only for development use", Toast.LENGTH_LONG).show(); + } + }); + } else { + ivDeveloperVersion.setVisibility(View.INVISIBLE); + } + + if (mCard.getStatus() == Tangem_Card.Status.Loaded) { + tvWallet.setText(mCard.getShortWalletString()); + if (mCard.isWalletPublicKeyValid()) { + tvWalletIdentity.setText("Possession proved"); + tvWalletIdentity.setTextColor(getResources().getColor(R.color.confirmed, getActivity().getTheme())); + } else { + tvWalletIdentity.setText("Possession NOT proved"); + tvWalletIdentity.setTextColor(getResources().getColor(R.color.not_confirmed, getActivity().getTheme())); + } + } else { + tvWallet.setText("not available"); + tvWalletIdentity.setText("-- -- --"); + } + + timerHideErrorAndMessage = new Timer(); + timerHideErrorAndMessage.schedule(new TimerTask() { + @Override + public void run() { + tvError.post(new Runnable() { + @Override + public void run() { + tvMessage.setVisibility(View.GONE); + tvError.setVisibility(View.GONE); + mCard.setError(null); + mCard.setMessage(null); + } + }); + } + }, 5000); + + + } catch (Exception e) { + e.printStackTrace(); + } + } + + Timer timerHideErrorAndMessage = null; + + public Intent prepareResultIntent() { + Intent data = new Intent(); + data.putExtra("UID", mCard.getUID()); + data.putExtra("Card", mCard.getAsBundle()); + return data; + } + + + @Override + public void onResume() { + super.onResume(); + mNfcManager.onResume(); + } + + @Override + public void onPause() { + super.onPause(); + mNfcManager.onPause(); + } + + @Override + public void onStop() { + super.onStop(); + mNfcManager.onStop(); + } + + @Override + public void onTagDiscovered(Tag tag) { + try { + Log.w(getClass().getName(), "Ignore discovered tag!"); + mNfcManager.IgnoreTag(tag); + } catch (IOException e) { + e.printStackTrace(); + } + } + +} diff --git a/app/src/main/java/com/tangem/wallet/VerifyCardTask.java b/app/src/main/java/com/tangem/wallet/VerifyCardTask.java new file mode 100644 index 0000000000..bc0b7cf576 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/VerifyCardTask.java @@ -0,0 +1,109 @@ +package com.tangem.wallet; + +import android.content.Context; +import android.nfc.tech.IsoDep; +import android.util.Log; + +import com.tangem.cardReader.CardProtocol; +import com.tangem.cardReader.NfcManager; + +/** + * Created by dvol on 04.02.2018. + */ + +public class VerifyCardTask extends Thread { + + IsoDep mIsoDep; + CardProtocol.Notifications mNotifications; + private final String logTag = "VerifyCardTask"; + private boolean isCancelled = false; + private Context mContext; + private Tangem_Card mCard; + private NfcManager mNfcManager; + + VerifyCardTask(Context context, Tangem_Card card, NfcManager nfcManager, IsoDep isoDep, CardProtocol.Notifications notifications) { + mCard = card; + mContext = context; + mIsoDep = isoDep; + mNotifications = notifications; + mNfcManager = nfcManager; + } + + @Override + public void run() { + if (mIsoDep == null) { + return; + } + try { + // for Samsung's bugs - + // Workaround for the Samsung Galaxy S5 (since the + // first connection always hangs on transceive). + int timeout = mIsoDep.getTimeout(); + mIsoDep.connect(); + mIsoDep.close(); + mIsoDep.connect(); + mIsoDep.setTimeout(timeout); + try { + CardProtocol protocol = new CardProtocol(mContext, mIsoDep, mCard, mNotifications); + mNotifications.OnReadStart(protocol); + try { + mNotifications.OnReadProgress(protocol, 5); + + Log.i("VerifyCardTask", "[-- Start verify card --]"); + + if (isCancelled) return; + + String PIN = mCard.getPIN(); + protocol.setPIN(PIN); + protocol.run_Read(); + PINStorage.setLastUsedPIN(PIN); + mNotifications.OnReadProgress(protocol, 30); + if (isCancelled) return; + protocol.run_VerifyCard(); + mNotifications.OnReadProgress(protocol, 60); + Log.i("VerifyCardTask", "Manufacturer: " + protocol.getCard().getManufacturer().getOfficialName()); + if (isCancelled) return; + if (protocol.getCard().getStatus() == Tangem_Card.Status.Loaded) { + protocol.run_CheckWalletWithSignatureVerify(); + mNotifications.OnReadProgress(protocol, 90); + } + + + +// if (isCancelled) return; +// if (protocol.getCard().getStatus() == Tangem_Card.Status.Loaded) { +// protocol.run_CheckWithSignatureVerify(); +// } + + } catch (Exception e) { + e.printStackTrace(); + protocol.setError(e); + + } finally { + Log.i("VerifyCardTask", "[-- Finish verify card --]"); + mNotifications.OnReadFinish(protocol); + } + } finally { + mNfcManager.IgnoreTag(mIsoDep.getTag()); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + void cancel(Boolean AllowInterrupt) { + try { + if (this.isAlive()) { + isCancelled = true; + join(500); + } + if (this.isAlive() && AllowInterrupt) { + interrupt(); + mNotifications.OnReadCancel(); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + +} diff --git a/app/src/main/java/com/tangem/wallet/VerticalTextView.java b/app/src/main/java/com/tangem/wallet/VerticalTextView.java new file mode 100644 index 0000000000..53bbecdb9a --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/VerticalTextView.java @@ -0,0 +1,70 @@ +package com.tangem.wallet; + +import android.content.Context; +import android.graphics.Canvas; +import android.text.TextPaint; +import android.util.AttributeSet; +import android.view.Gravity; +import android.widget.TextView; + +public class VerticalTextView extends TextView +{ + final boolean topDown; + + public VerticalTextView( Context context, + AttributeSet attrs ) + { + super( context, attrs ); + final int gravity = getGravity(); + if ( Gravity.isVertical( gravity ) + && ( gravity & Gravity.VERTICAL_GRAVITY_MASK ) + == Gravity.BOTTOM ) + { + setGravity( + ( gravity & Gravity.HORIZONTAL_GRAVITY_MASK ) + | Gravity.TOP ); + topDown = false; + } + else + { + topDown = true; + } + } + + @Override + protected void onMeasure( int widthMeasureSpec, + int heightMeasureSpec ) + { + super.onMeasure( heightMeasureSpec, + widthMeasureSpec ); + setMeasuredDimension( getMeasuredHeight(), + getMeasuredWidth() ); + } + + @Override + protected void onDraw( Canvas canvas ) + { + TextPaint textPaint = getPaint(); + textPaint.setColor( getCurrentTextColor() ); + textPaint.drawableState = getDrawableState(); + + canvas.save(); + + if ( topDown ) + { + canvas.translate( getWidth(), 0 ); + canvas.rotate( 90 ); + } + else + { + canvas.translate( 0, getHeight() ); + canvas.rotate( -90 ); + } + + canvas.translate( getCompoundPaddingLeft(), + getExtendedPaddingTop() ); + + getLayout().draw( canvas ); + canvas.restore(); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/wallet/WaitSecurityDelayDialog.java b/app/src/main/java/com/tangem/wallet/WaitSecurityDelayDialog.java new file mode 100644 index 0000000000..df1c2ac71e --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/WaitSecurityDelayDialog.java @@ -0,0 +1,166 @@ +package com.tangem.wallet; + +import android.app.Activity; +import android.app.AlertDialog; +import android.app.Dialog; +import android.app.DialogFragment; +import android.content.DialogInterface; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.ProgressBar; + +import java.util.Timer; +import java.util.TimerTask; + +/** + * Created by dvol on 06.03.2018. + */ +public class WaitSecurityDelayDialog extends DialogFragment { + ProgressBar progressBar; + int msTimeout = 60000, msProgress = 0; + Timer timer; + + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + + LayoutInflater inflater = getActivity().getLayoutInflater(); + + // Inflate and set the layout for the dialog + // Pass null as the parent view because its going in the dialog layout + View v = inflater.inflate(R.layout.dialog_wait_pin2, null); + + progressBar = v.findViewById(R.id.progressBar); + progressBar.setMax(msTimeout); + progressBar.setProgress(msProgress); + + timer = new Timer(); + timer.scheduleAtFixedRate(new TimerTask() { + @Override + public void run() { + progressBar.post(new Runnable() { + @Override + public void run() { + int progress = WaitSecurityDelayDialog.this.progressBar.getProgress(); + if (progress < WaitSecurityDelayDialog.this.progressBar.getMax()) { + WaitSecurityDelayDialog.this.progressBar.setProgress(progress + 1000); + } + } + }); + } + }, 1000, 1000); + return new AlertDialog.Builder(getActivity()) + .setIcon(R.drawable.tangem_logo_small_new) + .setTitle("Security delay") + .setView(v) + .setCancelable(false) + .create(); + } + + @Override + public void onCancel(DialogInterface dialog) { + super.onCancel(dialog); + } + + public void setup(int msTimeout, int msProgress) { + this.msTimeout = msTimeout; + this.msProgress = msProgress; + } + + public void setRemainingTimeout(final int msec) { + progressBar.post(new Runnable() { + @Override + public void run() { + int progress = WaitSecurityDelayDialog.this.progressBar.getProgress(); + if (timer != null) { + // we get delay latency from card for first time - don't change progress by timer, only by card answer + progressBar.setMax(progress + msec); + timer.cancel(); + timer = null; + } else { + int newProgress = progressBar.getMax() - msec; + if (newProgress > progress) { + progressBar.setProgress(newProgress); + } else { + progressBar.setMax(progress + msec); + } + } + } + }); + } + + static Timer timerToShowDelayDialog = null; + static WaitSecurityDelayDialog instance = null; + + public static WaitSecurityDelayDialog getInstance() { + if (instance == null) { + instance = new WaitSecurityDelayDialog(); + } + return instance; + } + + private final static int MinRemainingDelayToShowDialog=1000; + private final static int DelayBeforeShowDialog=5000; + + public static void onReadBeforeRequest(final Activity activity, final int timeout) { + activity.runOnUiThread(new Runnable() { + @Override + public void run() { + if (timerToShowDelayDialog != null || timeout < DelayBeforeShowDialog+MinRemainingDelayToShowDialog) return; + timerToShowDelayDialog = new Timer(); + timerToShowDelayDialog.schedule(new TimerTask() { + @Override + public void run() { + if (WaitSecurityDelayDialog.instance != null) return; + instance = new WaitSecurityDelayDialog(); + instance.setup(timeout, DelayBeforeShowDialog); + instance.setCancelable(false); + instance.show(activity.getFragmentManager(), "WaitSecurityDelayDialog"); + } + }, DelayBeforeShowDialog); + } + }); + } + + public static void onReadAfterRequest(final Activity activity) { + activity.runOnUiThread(new Runnable() { + @Override + public void run() { + if (timerToShowDelayDialog == null) return; + timerToShowDelayDialog.cancel(); + timerToShowDelayDialog = null; + } + }); + } + + public static void OnReadWait(final Activity activity, final int msec) { + activity.runOnUiThread(new Runnable() { + @Override + public void run() { + if (timerToShowDelayDialog != null) { + timerToShowDelayDialog.cancel(); + timerToShowDelayDialog = null; + } + + if (msec == 0) { + if (instance != null) { + instance.dismiss(); + instance = null; + } + return; + } + if (instance == null) { + if( msec>MinRemainingDelayToShowDialog ) { + instance = new WaitSecurityDelayDialog(); + // 1000ms - card delay notification interval + instance.setup(msec + 1000, 1000); + instance.setCancelable(false); + instance.show(activity.getFragmentManager(), "WaitSecurityDelayDialog"); + } + } else { + instance.setRemainingTimeout(msec); + } + } + }); + } +} diff --git a/app/src/main/java/com/tangem/wallet/WalletInfoFragment.java b/app/src/main/java/com/tangem/wallet/WalletInfoFragment.java new file mode 100644 index 0000000000..3978fee305 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/WalletInfoFragment.java @@ -0,0 +1,151 @@ +package com.tangem.wallet; + +import android.content.ClipData; +import android.content.ClipboardManager; +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Color; +import android.os.Bundle; +import android.support.design.widget.Snackbar; +import android.support.v4.app.Fragment; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; +import android.widget.Toast; + +import com.google.zxing.BarcodeFormat; +import com.google.zxing.EncodeHintType; +import com.google.zxing.WriterException; +import com.google.zxing.common.BitMatrix; +import com.google.zxing.qrcode.QRCodeWriter; +import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel; + +import java.util.Hashtable; + +import static android.content.Context.CLIPBOARD_SERVICE; + +/** + * A simple {@link Fragment} subclass. + * Activities that contain this fragment must implement the + * {@link OnFragmentInteractionListener} interface + * to handle interaction events. + * Use the {@link WalletInfoFragment#newInstance} factory method to + * create an instance of this fragment. + */ +public class WalletInfoFragment extends Fragment { + + // TODO: Rename and change types of parameters + private Tangem_Card mCard; + + private OnFragmentInteractionListener mListener; + + public WalletInfoFragment() { + // Required empty public constructor + } + + /** + * Use this factory method to create a new instance of + * this fragment using the provided parameters. + * + * @return A new instance of fragment WalletInfoFragment. + */ + // TODO: Rename and change types and number of parameters + public static WalletInfoFragment newInstance(Tangem_Card card) { + WalletInfoFragment fragment = new WalletInfoFragment(); + Bundle args = new Bundle(); + args.putString("UID",card.getUID()); + card.SaveToBundle(args); + fragment.setArguments(args); + return fragment; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + if (getArguments() != null) { + mCard = new Tangem_Card(getArguments().getString("UID")); + mCard.LoadFromBundle(getArguments()); + } + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + // Inflate the layout for this fragment + View result=inflater.inflate(R.layout.fragment_wallet_info, container, false); + + ImageView mImage= (ImageView)result.findViewById(R.id.qrWallet); + try { + mImage.setImageBitmap(generateQrCode(mCard.getWallet())); + } catch (WriterException e) { + e.printStackTrace(); + } + TextView mText=(TextView)result.findViewById(R.id.strWallet); + mText.setText(mCard.getWallet()); + + mText.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + TextView mText = (TextView) view; + ClipboardManager clipboard = (ClipboardManager)getActivity().getSystemService(CLIPBOARD_SERVICE); + clipboard.setPrimaryClip(ClipData.newPlainText(mText.getText(), mText.getText())); + Toast.makeText(getContext(),"Copied to clipboard",Toast.LENGTH_LONG).show(); + } + }); + return result; + } + + public static Bitmap generateQrCode(String myCodeText) throws WriterException { + Hashtable hintMap = new Hashtable(); + hintMap.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H); // H = 30% damage + + QRCodeWriter qrCodeWriter = new QRCodeWriter(); + + int size = 256; + + BitMatrix bitMatrix = qrCodeWriter.encode(myCodeText, BarcodeFormat.QR_CODE, size, size, hintMap); + int width = bitMatrix.getWidth(); + Bitmap bmp = Bitmap.createBitmap(width, width, Bitmap.Config.RGB_565); + for (int x = 0; x < width; x++) { + for (int y = 0; y < width; y++) { + bmp.setPixel(y, x, bitMatrix.get(x, y) ? Color.BLACK : Color.WHITE); + } + } + return bmp; + } + + + @Override + public void onAttach(Context context) { + super.onAttach(context); + if (context instanceof OnFragmentInteractionListener) { + mListener = (OnFragmentInteractionListener) context; + } else { + throw new RuntimeException(context.toString() + + " must implement OnFragmentInteractionListener"); + } + } + + @Override + public void onDetach() { + super.onDetach(); + mListener = null; + } + + /** + * This interface must be implemented by activities that contain this + * fragment to allow an interaction in this fragment to be communicated + * to the activity and potentially other fragments contained in that + * activity. + *

+ * See the Android Training lesson Communicating with Other Fragments for more information. + */ + public interface OnFragmentInteractionListener { + // TODO: Update argument type and name +// void onFragmentInteraction(Uri uri); + } +} diff --git a/app/src/main/res/drawable-hdpi/ic_show_menu.png b/app/src/main/res/drawable-hdpi/ic_show_menu.png new file mode 100644 index 0000000000000000000000000000000000000000..e7b9bd3ec0e1afb992571433cec0a3739cef0ee6 GIT binary patch literal 144 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1|-9oezt=sPZ!6Kid%25Zsct+5MVwK!m;tc z3rFC_%ggyiSIQKsS2BqI&*Noin7*oN(xL-htm~GX5wV!`Nc-S-<$IHjf*<&Fsm5(Q rFjvHz$3EtfuI98GLfQ-r2UajQhzb5oaFb30TEXDy>gTe~DWM4fNsur; literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-mdpi/ic_show_menu.png b/app/src/main/res/drawable-mdpi/ic_show_menu.png new file mode 100644 index 0000000000000000000000000000000000000000..615265ea036c079a0ee5b9f77de7d183501c5e4b GIT binary patch literal 123 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdzCr=m0kcwMxCvW6rFyJ}-DSF{@ z(O=Q^Mc;g4Z6s7KygRsxi9y1n%1@asW_gXyqNb-)c WX>nhFDWn2TWAJqKb6Mw<&;$UY>nQX9 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 0000000000..c7bd21dbd8 --- /dev/null +++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + diff --git a/app/src/main/res/drawable-xhdpi/ic_show_menu.png b/app/src/main/res/drawable-xhdpi/ic_show_menu.png new file mode 100644 index 0000000000000000000000000000000000000000..efd69d739f8193fae25ef465567689e511ca1d29 GIT binary patch literal 148 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=(Vi}jAr-gYUfamapvc4G==Hnf zQpY}%DkDv$(vnL(CA>LF{ft0$A6BfIYs1JQ;LwoQFw-WThasx|fY&_#00u^u7i>Q! qKks35c+LM~(sN-E2cQs81H%rP3gN1${}d-lf_R>;elF{r5}E)zJS{%} literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxhdpi/ic_show_menu.png b/app/src/main/res/drawable-xxhdpi/ic_show_menu.png new file mode 100644 index 0000000000000000000000000000000000000000..82061f9137c5c3f7d6f49add4ecae58fdba95f56 GIT binary patch literal 207 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD1|%QND7OGo(>z@qLn>~)y?Kzc!9c*}qKN{> zxA+c)8x6K=%?!+ATlTOAAGBAs2I&9;gT1SE>G(4+vIsbUiH7w|GSjXLCTzCRcLge_ z_^q9^bx$9|?x&?JAKt!^Q)u7>B7^jX%b%>6cKqGN$;`muF#Y+?y%BNl4BO+)6TdSE jJAhHbCa4Vz3f6Xi@@54ZTQ_E-Enz5K6$1 z03tR-RB%L5k){YTDBysjLy@r}iiH7DvFijGMAUI`6dRUFWUU$Bym{}eS9UO(Z2>7`&z9wUXbV-Il z#&6`Y8GKGQ04S2&F6MJnWNa;Ck|;8QE#r9r;7G||@X{|>%+C|c55>;RS}qbKr-&IQ zTvLXPlM{>K&(BTgi^a?^4mXV>;xX8n8Ce|RasXz}{8imI52H3ZN4bf ze_i~WlJ|C&UW9+{8AKoW!}eExnGFE2re(F+`iE_46#!l90Z_aBhs|Iw0E)7{bq;-T z9=d#9QpDmcXDh4R++0fmpKB>E=%LdZt9g z$j;($`3&Zthxi`{{&gM}5&R^+h%b~yM9Zd3AWW9ETgVfL1(`yIK=_}U_z%PWq}jQa ziQ4!P(3V&Nr6C$XejWfQDiI(Fdt@un?|lo#M+5oIi_w{wo%_#%{(V=tO#a9gB!7-$ zM?^BX5>d|Vn*3S!?g~$*UQipUP zL&zMmg;!4Do9IA%up=Rh?=qPj=x&RGBx1dpI68aT- z2O}^EromdU5o`ssU{5#*j)WJ%$?!5bA1;Eoz?EiTr=n?cd`V|I)p<|3O zju?MT93~aB0<#&j8`F+Cg&D?-VWzQItUA^l>xvDRIYI4MQ`g1<+DyrL=EogS06Xii({|v`U^zjmmKqDIK93(F5q| z^fLNk`gQs{RV`IdRle#b)i%{Ds;|}NsClUI)k@Ub)kf6bsWa4l)YH_rsduU0(?DsM zX@qO!YV6TCtMPOWZH~(v?wpc2hv(eZgf-1HBQ#fN?$aF5oYvCT^3%%Fs?s{6^;Da# z?V+8jy+iwi_M{F~$4y6|vqR^k&SQoO!;_KDsATjprgSxR{dFa}^}2()GkV5)QF?`X z?Rxk03HmJkB>f%wz4}uIItC#I1qQ7Kw+-=zEW;GTU55RJuZ@h2VvIHzbs0S}Rx=JT z&Npr~zH34@aW`3J(qMAU6l2OVO*7qXdf5y%vo}jIt1%lghs_<#1?IcWhb_<+P8LFo z28$a^64R5J!)#@aTGB0pEekEXET35!SjAgyv+B3{Xl-wuZrx~o$A)4PXj5p@WAm%6 znJw40#`fA=@?77!tLJvleQsxN$G6*KchjC~A7a13zSsVPgQJ7Uq0M2^(ZDg$vDWbh zi^d9LZDyT!LOXdmt#&%*^w!zIS?qk+`4<X~g?%562@eae34a)26HyS+zks@6 z$%2*zuOhu7%OdYYnM6sVdZQJi6QY}=U&naIl*dS8tzuWkUW(I*6U24LW8oFzvR(TOpMEs5_rp_~TJ^wNN(wM(bC zZ0;`Z6P^ce2XB(^$}i_nB)KM)Cp}7bP2Qe7nc|*Ok@8f)7E}wKr~0SXrM^xJP1~RL zDLp2=Jp-4Km~m7{5vB?IGPN`FGKaIwvx>8%%bb_(Ts9>N5;bK**^9Ef#WdN^)PTf9 zvR*Qp{o-l7TcBI8wqSIn=gRt3(5j`Y zdRObOE?Pal#&6AmwS={4Ykw%TE-Wv6xh`g1Pmxy9nxe7we(PI{6^cd0H#WFzsN0Cz zDA+i-Y3`<~O&?2mB^OJrODjs>Z{}{k_?699m0x|@lC)*8%%N=0R?Jr6*6Z8cw;d=~ zF3&F?+a9vLa|dHb$&Qyhm+ZVyVOLSNi?B>BD~E ze(8aT1AWbo&CM;EEoH56tE6@EV8X%6-*|u1-NtOIZ>P7H9s-9XhaP{M`0e$>L5F*f zu#U8SXZT%h2eqT56Y5;vIn|ZYCGC#u9zGg)w718lr{jCe@An_mJyvsE<#^c%!il02 zpHAkVoIaIx>gnm^(__6$dheWxJ#(!uyl?Pq(Ao3ne9xWf_v}A;-u3*k3(gmgUSwVD zy5w-FbHIL};|Kd6ItCpEJBJ*Hx-UCj?irppeBz4xmD5+fub#UWaP88_{E^}7QP*$Y zNVp-r$-DXJR{E{yw{vdK+*xxMeYfPE(!GlNn)e%iH2tw%>L5Kn>ODH}V8MesW8ASP zKV|>)e!S=*`C-L`&P4Mg+egPHeJ3wJUif(YN!F8@r^P=j|6Kdbc>FRj6+1Ql zT=e|YubW?}zu5oM?q%30zZG z_Qy|H!V(ixwRNWc+kfeFsxuv{ZJmGBvC=YC zv|869C<>xzaUq~0tL%$_fGi;-toffC!qlZGOKvvu^YcEzM|k(HGr_uTu=(-Mh9 zpq5(FmX^Fbpq6S%4VGF|qz3B|H8(dKXJ%%)Pna;_I+Mw4lXriVS!%F;mxjUlgVQtr zcIO?lBW8bcaB#?ycZZZ&YOs2gmNdTf!Y`|hd1h0R_8tj!`0}H_;$HR0W`I(IMI_|u z!Rha38uIQ3G#WZ-bVV1hPMDc+H6S41g1oz?tWtwTl*M&}q=T#59beRt?BP7zp18Ex z+xw=0fkC5O`=+E)gC*n{z=3V=;h7lq=Z z8oFSmB~5^o$SDZbu-@K+R*TV|*6foJb#A$dtL>*4$O~lIMP=4d1xpJ9_EpdYFFx;$ z+ID)HWqVSW)hw8D!XUsaMHZb?1r1HGPVAip_u>}-6LcQkEo2(?_7|2Hzlz>u?mcE1 zTCEoP9aMe|J+LbBM?l=Zr652T*n4dCn9yoLdtBADJ8{WDFNFpjquW8%&`<+Q6V4r; z1$o?HVE6B{BV49Ae~($^IPHbY=H})#v^uCN8d_kLWZUu2?_0q#1dZpv`Dt}lclh4i zb24o9C#%1fcGpx*LkBD&Pa6(yU&E>?ozzzs_@p#6yNoNpzp*y$6|Y23PtQBj?VxID z=zx{Q^@PO8*9Hm|kp@Eq?InV-<=fA$bp6qFBa_MOl3oW@OG5&zwzf8H=AIAU2R(2c z?6DXe1E{!E7Mgl0AqE5a^U~;_Dr!i8#XRukA}Hie9E`Q<5t-@{l{62ph`h42X4(rk zF_3Q<{83d;st;C0z6}QKn^<~}uU;68aD>RHKU+MfGCuu;d05~{P%j94N~#Z5)V3uU zu)9dZT0JUsiH4~=POY=?aY>^3}L2b1jk?Mj)7VyPHh5!%jq;0KwMCK98|2Jt}!D9c@4h{|lbXrkPsV-P?(bM4O ziM430m^7Q|0O9CMpZ~DY;m5zN$N#F+4-%;+SP#l9A#UGd&;bqFpHR<^4ILM1=KOXv z?vy(H@VitKteB_;kjI@5Y`WH}M;e-4b`_CV!kdHqZm4g*=$7h%Rg!H7Cq7v!E2xM_ zM!q4Vc2>sTn8m)E7Vef+yNWE;1M8djKE_DLOV-VbDw<8Rqm!3e%$s-?1NkCZx36eY zEwHY}Oocn~FW@Lp=AVnK*%G8xO)S};IL~3L!;nuus18^{9t#eC@@E_cj+a*#v^i#) zCDGTHmIqIYv-WY$kyk$yL8=2*^f#|T@vSNH{6u6PvAsm#nh|wwrG@9{zc881PWkml zzETaaa=8xt^ZQn_x_~9`uAocQF58nDTCpJG_^2U#^Mxvar3Hz{X0vKaU!av?@2?mf z1FMUhBO^E|ZgbFTwadFd@{sa_bv4EdVzzyVv6Y^@yV4_ciOlx2=IPvB$4UAjO5XjE zhm;#EAx~dCAJ_^;iejyLq-m?Kk+bW8;@J+IN_qE3)>3Y;xR-n(<-{6bgR#83 z@}x-86y=$|KlV-kt*f`myFap)@`5Gg8ADXW284<&@2>Qwjt)(&lIZJ;iUK?nFp$3? z@BYYA$_v&H+rpucJ5AAC;G>eHAKWtbp9{72cFEK>(B;d!PqLD7f|bj4;$J=(!ZK83 zth$A&@_}{!PyiIg&qJuR<=vIOhy?K(c6v~AHfL%@;F9CKPVGe8jy5qCI;+52o*!#of(KQkaxbe z{m9y&-5Vq1-51(Y9^M^jgG9(WPyt$12;C`c8RxR;;y;vr4BQlmaaA zE%LEH&0BmmxdnlW4c3D)Qw-Qwvh={3dRsi`fXQLMay+1QOiX!bW|f^L0f3^ICDGTHOKQm9 zqS=r1zKGS}m2T#;mwtr6sxXGcLTz(g0KHZ3&>0`K*?RyMSHHiQNCcxFQWpylBqQ$2Pq_2;`GANZ^KwMrK73GR&5|cYxOH@{5!n@!)%BtCv?anFCFisAD_KGIjg$&DP#7X{xmPK_S6nUOF}j?w(kTP|?p> zwE~YdB`g8Pc;)_gpRQp$Ecs}s$JGU1mHZH=E)hdlvT#r8BFC*co8z6j_vCA;lzvb= zurOeUqhGB-_e6g_kUY=J-YW){e{|rf-#L5bao$|>-S~eeeSYJChc0E8XnuF+MY4?f zf~w3TDjHa1K5#nZac2Vi@yC%+kpQY8JYqjusHf|Q>Ke8}@QPUblvA%2Uu*Dx*kYtb z(3n#Ww*eq=+??u~z;)`$$HSYTD;bNDCzxt?VxgZl&9;66+YIg9~&Qm=zf&ge3B z$olA;>3Hh~_vQE#gaMBwC=4l@_VLtiVxOq!G8I6X4D>1^h@v>G=S|Qd+S&v(+m~8ZChqT;kOa9hx4JWz3sF39B*77p$}-SN`RLWc7k9a#p}{9Ggz! zr2!v<17fyfw2CK5v zUKTgWqqRz4ZTNaS4EEda>~)7TckWm2N3%Y=@xa57s!O!f8+Cr+j!(n#z6(*da!B$6 zi+kw>xN>kcLPdAECJ_$=tXEeb2D@>^gFcq7VH?a}cgm({T}bhz2ET?@18P=_2wkEJ zch~VZFpz(G+DU;WAFzZxBZ!Ll6FNX$>TR_FnGcMG#TyPu?U-Y{3N3tJ-90ij)+4RO z%}9gpg20g&$iIhyd?E(&{Ts>3QSt#R`kRGN$eo7)JAJVD&;;QT-=U?E?n1gb;pe=r zolGcBY6_ugwIqc_+|&2RF7e;G`b#Q}P>}4wdQdi;e}3O$mMoy+Ayk1Yc7oT`t5QF< zh0Q|*cBjwnH*I4p=6lh!TAH@{+GH7XdGMqJ4CJ}g8j_-94Hg4F5B@8wsdTc8mK&F` z=C3;<9?j9vq&ZhOL9^mrVoD}t*Vt&#T(c!8sUMvYb#A_;t34@)B&5=i6eU}*uEtD( z@>^~Q75xQ#0%&2)4*M44p)886h+uWZ|9RvFOveFmWn`5SZcjv`-CG&`7G}{`CWC4~YBpf5ljXh;lhW0kRwVfA>N*tz`FR+~ zOK-YLN}>lADe!;vtF>S#1E_c)1h?@QF<|fea${Puu&II2O$Xi6Kl4qhoI0A8)gr)l z0T^dTC$GXl{wfCYrBoUkpy+}1!?qPr$PJW95n3Inh2YJ*F<|G(pta{~#-ez~#G}XV zovQgW&0F-zaRq6$FXfy}eig0OUg*9BS0O|PtX%F`{^f%KEIk=i;H!srm=+cVesiu< zZ>#ST!NzaC@kpHTN_ERTSIQZA-JD$)-nE(Jo}frib)^B87X0|d3RX?&Oj+C_&$qHI zWCgD}E?y_4NV2f0xH=BkR7 zit|)g>0W#x&k*8%T!z8)2$@(bBij_#stCoc(1J0a#q00=!|~Q<&Xs&Wdzvnh!7(to zxH5tToRFOY|e`M=smW~W6@R6~Pdtgl4lFiWy|C}aW zLm|vs8s(ewr*VR7Ix`wt4Rk2p?8?geHetcod46Z*eLte~z&ahd5Cis?WRPAEp|vW6 zwOhYan!*{L)3VrOeJ+j6$PT>K>O!@t&e9g&ffoem~k#xYy<&J}6+g8dX zXH^e1@Ww~~f-zp%GH6X&62Yp7D6is>@x@8aVKg)H7;9J(1Eei=+HCO0iAp++hSJmiesQa$-q8|WaNWZA9hdQIrGB(m(8fgS|x&! z8T-$zvG#VkE3dkyg9c0ceAFwD8$UxP3;1}@#9I2{KY=q^IjgXQtc`WNaW3lKsp=&a zP3D#q(+{K^(yQ60F8|^l78pmRk>_Y|U_B_agt&bR@TXDI>CQ96gD&t4jD=})6G7hd zfdf|;y!FL5UKgEbRJW|Oq}-NS>W)(zIsQIec|6s1a9|zVJs%3Wvt<%n8DeY|^yW|E z+pd&ZcH_z{*L?on)RVSagnhTe8rc@Mz5adJ{Y7Oer+diT-9A$h+*4^hErhk>MCi z&@?pwQDzfa$4p(N1oIGld7F?Xv?Z@A? z_I9}~tMo(v!HSEX4QcVy&{|3NHYbVO%W+u%>BBsMn{EOPS-4Y(CtA1nyYRIUaCAlB*N2wmT7xfRU2W1xg zBU?AH*kB>mULZfEw$tINjX&g;nLy>)cKlYOx6o#Qruh6mJO0poPJE)Uzn8G{FryP1 za&a4P4CV{;f$dfU9T#fEzmuW`Gs})nehUNnOBl!(N$a4!sefR7AF-0vRO*K=P(xZB z;OpALZ{rsOw|OKKMYoE95)q)7ILwj76 zRIHa|8S}*S{jrPux32y>P_Ap*cd*iuydfnrL@H2`wNwV(anuDPFDSdDg>Io$Y@NDK z^3VY4N%a6$azp^Nc3LnZ$OSB1&BX4JQP(=?0AJ6JtwG8m6$N;n!$6*DIi$XUMGAWS zuyGYKK5)hc$$m71$?+m-d zN~KI(2V7%!$Ul!jm9+t*U8Dx8pX#w*#68|WD}-9M7I3CGfyLAofFt?PS>4eu_lWP+ zMvFHsb??bA$zxTdu4jXl%eAjhI`l5vupb3Idh$`@7Qn?({!owv(0IGsm1~j{q;`7) zRu5o;hX4gfTcO^K37qgTz$)iM*UyE&zpoF|Lz=ewaChg$w?{6X8fRr?_57!x^;5lq zC7BNlXW!2n*{9VD5}>bF zlqCQSLk%@l6xCzFO3$=Got*}lk@#z!DTpxsVqpB$hx7!a=4#QaMG4$k4CF6Lvu}S0 z7FsJNGb-X8(0dM4lE6HFrdGb8#G{wo)=zkM$z+kNIU?Q0r`hE>a&%T+M)yP!8?A%s*;qc8TiG z7SFFpyp~`Z7WlKY`tvkc75O&&^ZORC^m>2bcj?IKkml?Uw@Xan{y`xE)$I?}`6&5| zgdSvqyYY5A@Uj`=_{(yY3Fus}5a++16k06?I?G|HJ5H^!@p1W0S_=Z71WN<{wRs(@ zrZng=Yb6QH$-LerpXG2{U<=h}1o-U`idP+f*}vT3Ng&%U|o%QkzdU9MQhc4U`b$3=Jm21XTk-2 zA86zXq3%X^X@I`0JWxx%>hU6ZKMA4AnF*GmUSRRCTI?R-k@-hb4yoWjscW5rC&gO( zIOh(w-QU3y@(keEu9ax5ri!iABMa_G993GX3za9Ep}namFjq^NM6Ox)`mbhalq}CN zsc!+TBL$C+9<5V8JGdz{?u)fBNQ?11SjjQIkQ@I>&wt6h-kmXi;y9`{mgJ+lr(x7k zDX)4`KeZ_xz%S7NQ&$r(+b~g_|LW?_f3Yx-4=+mK{xT}SYfw-1=mzUSnI%8+FCVe^ zFdU|Z!AWx$$f+6zRcAVoaG`jvN}Z2V9g%A(PEVk*sJqJm&J>Kl)|rClrE=(~Xl5p@ z-2J}!-LJ117#K7Sv<)rs784bOuj63HMMid~Gm>e2Z}GCfpf z&xFx4oIro7HHePnPtDjHv&?tXLQ>j%KpA;0D9I*OjVB0qWDLJ}As0@4aRxet689<; zJ+{nH13_cZuY9GJj3w&q&VQw4b-*xhT^PB+0$ZMIuoElgZT9)Kr@?RZ(YW zr%p*p$?F9L1(^;G4g;zmFAm=OQosxYIUd=tAu^YEyRotHbW>9k3jv!^T3R}`zP{c> zAP~4S7>r4}y7ZsQs}?<>kzgWRGxGEEf3mc+ti$Wx?|Tskb|*A~Xl`!aL^cHNKQ1mV zS_qXkg2qA6CSr#i(3+7N@9PmVI(JK}MO6rwd2Vj*`|j@U@5l_SCxs1nkPSi0N6_*o zOqig7R!tvW=Rt(b5zQ)jPT?!~EqEEa*UXbmN9{fpT1;(i?Gab5T=5JF3L;jkUrU&& z)Ivau)`eR$cIGyMcAM<0tgJkNE^{<)76_e1t4Oe^mDjG<-rml@55Q4Frg*|@GU5LH z`|s!F<&}DRdZza0Z)H|w!^+C4nrzMRRBs{A*K;s%C`W51&XLsA)Nm23VG__@uc#@r zYJS+Fk0Z{R_`%dJQ;s6%UMLjKxPJZm#ejf-K9`+Th_XN!n>CY7GPJ#hpcx@(#F9-V zBTjrBj@E1pS~Gw3h*S2$b3Pv3;RFoiZ{T&R>&xFL1Xxe%krC$+PZtl(1_4h8L9;@@ zCg5Pep5)KaU2ep;veed0W_m9vDd}ElXz0uBi;!V%|m=>9xp#Hp_aPP!uLhx+<@`+|Z3N4yTted>k6gY~2^ z<`N-f4Y-L%Dd-{@Xu-~)%e0e>I4yC+`98*I;-%GS#%*9AWdYQBO8Djd^xCy+0vj8f zV!RH|tjeo&U_B`U+M^@R=H_Nrb#?VQbemQ<5Fjq}4g99L3j~5;7^AtWN1Ou}o;N)_ zpYNonr=P=fod?fY45;*AJt>kUCR@*l6G2Nu&`cyF&QN@{Knush5$7uirP^ioUzQl> zuClkc{~LOZUW-Sl99U0^EyF{E=*o z%F4=k9Bo?RMv}B2;E2;iGRJ9$mQ8P{LWW_(tel*j4tIC=JRCSZwPs5V*0V)MoRwsg zj5re|0|8SU2+Y7&KR%ytBpGpf4z&>{y2Lq&iHRpdLqq@b!~u z8UZaGK{G(mbP=@4xH;fSw?vmoR%^^vRmjj%O}v$r=IZ3+RDfT)hMxUuu%sw5k&Z9T z2--<9^&}Z_TH}Z_s-&{WhQwz0`%Tb&Q;K-{N!fuT{m0Ms>GkW^3jzWHuJr6zgGEVX zMu3#N#&5BBj+5-e7|j?*oN;K`tk7*v!Ceb+)TxIfEHrA`WR*fI=%L^8NhgQXxsYu1Ka&#KzH=q`UmuuK{o8_n_O*AO%_+*kjZ8Z4!8S?Bp)PE%g@h$9U%j>ZeV0&)boDvEBU{cmKJn$bmuO~&v=Zdj!)dIvGLQT{Pp$q#eYMj zrzb8qRFB)oJ;x`)euzX&@pNj>5*k9eNDY<}ihu@8=yWh}IEJoMz6Hyku0$lZVrsC| gq9Qd|YEjYu1F>wpn$nD5xBvhE07*qoM6N<$f^xf78vpf6Xi@@54ZTQ_E-Enz5K6$1 z03tR-RB%L5k){YTDBysjLy@r}iiH7DvFijGMAUI`6dRUFWUU$Bym{}eS9UO(Z2>7`&z9wUXbV-Il z#&6`Y8GKGQ04S2&F6MJnWNa;Ck|;8QE#r9r;7G||@X{|>%+C|c55>;RS}qbKr-&IQ zTvLXPlM{>K&(BTgi^a?^4mXV>;xX8n8Ce|RasXz}{8imI52H3ZN4bf ze_i~WlJ|C&UW9+{8AKoW!}eExnGFE2re(F+`iE_46#!l90Z_aBhs|Iw0E)7{bq;-T z9=d#9QpDmcXDh4R++0fmpKB>E=%LdZt9g z$j;($`3&Zthxi`{{&gM}5&R^+h%b~yM9Zd3AWW9ETgVfL1(`yIK=_}U_z%PWq}jQa ziQ4!P(3V&Nr6C$XejWfQDiI(Fdt@un?|lo#M+5oIi_w{wo%_#%{(V=tO#a9gB!7-$ zM?^BX5>d|Vn*3S!?g~$*UQipUP zL&zMmg;!4Do9IA%up=Rh?=qPj=x&RGBx1dpI68aT- z2O}^EromdU5o`ssU{5#*j)WJ%$?!5bA1;Eoz?EiTr=n?cd`V|I)p<|3O zju?MT93~aB0<#&j8`F+Cg&D?-VWzQItUA^l>xvDRIYI4MQ`g1<+DyrL=EogS06Xii({|v`U^zjmmKqDIK93(F5q| z^fLNk`gQs{RV`IdRle#b)i%{Ds;|}NsClUI)k@Ub)kf6bsWa4l)YH_rsduU0(?DsM zX@qO!YV6TCtMPOWZH~(v?wpc2hv(eZgf-1HBQ#fN?$aF5oYvCT^3%%Fs?s{6^;Da# z?V+8jy+iwi_M{F~$4y6|vqR^k&SQoO!;_KDsATjprgSxR{dFa}^}2()GkV5)QF?`X z?Rxk03HmJkB>f%wz4}uIItC#I1qQ7Kw+-=zEW;GTU55RJuZ@h2VvIHzbs0S}Rx=JT z&Npr~zH34@aW`3J(qMAU6l2OVO*7qXdf5y%vo}jIt1%lghs_<#1?IcWhb_<+P8LFo z28$a^64R5J!)#@aTGB0pEekEXET35!SjAgyv+B3{Xl-wuZrx~o$A)4PXj5p@WAm%6 znJw40#`fA=@?77!tLJvleQsxN$G6*KchjC~A7a13zSsVPgQJ7Uq0M2^(ZDg$vDWbh zi^d9LZDyT!LOXdmt#&%*^w!zIS?qk+`4<X~g?%562@eae34a)26HyS+zks@6 z$%2*zuOhu7%OdYYnM6sVdZQJi6QY}=U&naIl*dS8tzuWkUW(I*6U24LW8oFzvR(TOpMEs5_rp_~TJ^wNN(wM(bC zZ0;`Z6P^ce2XB(^$}i_nB)KM)Cp}7bP2Qe7nc|*Ok@8f)7E}wKr~0SXrM^xJP1~RL zDLp2=Jp-4Km~m7{5vB?IGPN`FGKaIwvx>8%%bb_(Ts9>N5;bK**^9Ef#WdN^)PTf9 zvR*Qp{o-l7TcBI8wqSIn=gRt3(5j`Y zdRObOE?Pal#&6AmwS={4Ykw%TE-Wv6xh`g1Pmxy9nxe7we(PI{6^cd0H#WFzsN0Cz zDA+i-Y3`<~O&?2mB^OJrODjs>Z{}{k_?699m0x|@lC)*8%%N=0R?Jr6*6Z8cw;d=~ zF3&F?+a9vLa|dHb$&Qyhm+ZVyVOLSNi?B>BD~E ze(8aT1AWbo&CM;EEoH56tE6@EV8X%6-*|u1-NtOIZ>P7H9s-9XhaP{M`0e$>L5F*f zu#U8SXZT%h2eqT56Y5;vIn|ZYCGC#u9zGg)w718lr{jCe@An_mJyvsE<#^c%!il02 zpHAkVoIaIx>gnm^(__6$dheWxJ#(!uyl?Pq(Ao3ne9xWf_v}A;-u3*k3(gmgUSwVD zy5w-FbHIL};|Kd6ItCpEJBJ*Hx-UCj?irppeBz4xmD5+fub#UWaP88_{E^}7QP*$Y zNVp-r$-DXJR{E{yw{vdK+*xxMeYfPE(!GlNn)e%iH2tw%>L5Kn>ODH}V8MesW8ASP zKV|>)e!S=*`C-L`&P4Mg+egPHeJ3wJUif(YN!F8@r^P=j|6Kdbc>FRj6+1Ql zT=e|YubW?}zu5oM?q%aUtYAQl+(do!toXINjEM@NGKjDfMi_wkmgHh_~_L- zS`7zv^%}aoq|SJwtj>5Xqe!2YS)xz7ctf9ksZd+v$a2O7P~xv1l`V)5;lBd+7Q?+A z`W_qvo$SS8skOwS;3H_Q(liG?RHsHyoOyX`-ON>o>n=@mK%OD)d|qM)e%Ikqf+4+v z`N{j9bBz`8Nna9ZV=%&prU@MXNSo8sbkq5`2pf2&5Rn}MTshDHNMdvs0j4+9Y3Rb# z*FDc3zpmT6Jxz1quS=@S_gn4@h1z=fx8&1@<>%)Q6tB@4=s-i;8>h*^Ki0%*=JmB6 zKw=QN<%W%Oeye$A{}pxR{pL40UK}+(Ryh9Hi*Dn+q?{OdV7}e}EPw-*uH}z!A)Mj1 z=(wE+m&YZ7J9u(~qr7?Jr^buwbKmniwf~BC^X3bhzjmal3hw7QUiiIw=j=qu;19+~ zf0j^WA6?s$Tc(%LW^F7nMH1%=^`AWXQ`M_iigo&X%_q!*&z+ti8uCat;jHhUkPm}X zQmvl8CnB{3NII-=oxyY;F{sx;lY5H>P3kFn`^0}d{#0ZUOw6J%jjDE-&`l{zHoD(~tJl9=Vr1_g+?4^a>G7`gNLoWuPl} zhNcaXtIZJ$NM+oesaY^;RmGS0n#a4NWdn~abbHfB#u7}&+aslKy*6Aj4G6`rZ)1dN3_VCOy_V(y!5;g1qHN^8=iNVh?XSA!C$DQ--S3%oL7-xci}4}4Yr#l2j=W|HEuU#H7I*)6%l)doAwwgmN$s?mkYlsV^p8Mk|h15^MnXJ8MtJE=Q6T}km zNFu#Rzhd}TkA|b;duQ{7^~Z_Sr$wE?Z_P zz8CX}-v4bbT97(;md!B30adDzKTP~=InO=YNnx_7d?xLwbPL}_}A&FRH zE4`LhB^Eq5s(}l$eyy2xI#;V}$+e$9A|J7Qyvtf}=Y6zoD~{t3p!5aj61&wxaL=d#@p$>8Fa1%yhv~J?ZK_?ob(W$(mqYY)roH8KVqr%wyr}$F|Ehg?>wCFO;ny*0HO(wKXo@AM7Rh!X)u;AB~lbS+uq0D3j|BTvgYB8lAsrsANC9 zJ2A_}ybY%l3p)HT&@d~CBM!aARW5$a2=x&6JVR2JoS>tXE6c1*PIpKaw@T~l8UR1+g|;v zZsQ{iuUU{RjaoL=g z7pg_Ao(@oE=QuK=jsaO{t%v6PRy*U+HEr&VO1*I?oS)E5>^m$<@F8I!g)zkvr2S^ z&nC#S**`P zQd!QY73oWA)W$R+kL-uU0s^`ZilDl~Pdluy2Z?(m!izUq-^zBDH9*SBUL7X+A})X$ zcA-$KwU>Uu=IX;GAA0YUP~?MbN-d`l3o5Go^F#Hk*E38HFY0ZBkq&WmInYiMQ)I`a zNU9Tjy{(cj7i*2+H688h&+BJ2Gz@3y70e$*G$PjV>$;QU^L!Oj^D7psdep!z44=8k`5Mlu8Vv z`VdycLYyhk9qAUxU!*hHSu_#k%IVw5hvH-c=`3kda&=iCA@X<}q65a`aPkC6{+kD0 zhZXJYj$a%V0gdI2$?=jc>rN_f+Dre(&m~DJ*q!U?FXx22NjMRZhrpTR(1FxGFAf&J1cwXl zr3J6t`14fP)sOcO4r0qgWR+NGESD>d2f(moH01W>A(986QiiHn90G$9#aG7dofs|< z9?jOME$7eV>u=U+=qwRM^h8M{ph$U$8_%DxA{JuAs>)ZOvCyS8O|vG+K=KMTHyAQMn{wOQ{Nwid?K_*5WfRqr4G|?YK^BCdF%pvrQ&S9ZvUYy?V;V7 z+O)=VIV$RG&*aOwr9%T6J+wP1hv-*jjhr@aH((b#gu08pZ z$EFy6-Xb=pkFiQDFqUs_JgeTrgji1{i2A#V$N_9md_bFs%OR-bNNOO_$j%!5b=BJ? zHF`rN96#=Wdx7LQ`@&%9_@!f9_G|RCuLH8Q2@HdTd2lDq>?>LJ{c&ZUz4XvmGe=q|s|2D(*)raGRKRP4g4YrnIO1x+a9NO*T|Lv?g^_l*n^CCXk+tCu0HDd9( zByliHv$Ye0v{UrmvSawmwB;JTOH+) zJ2AtO-OBYtqXe@l9+ALyKOK&{>J`L$a$tnu(?eMrrLD9XMOyvt`R;!YilBNsdS(x6 z#4^%!>GpK(@n;yMo!nbA0a}cUgKk}wU1m6Vp-_M1&^7HbB+`WfZ4It=GQ&YPXeln; z(CRl#cU?1~n=paxd4EV;icdz4j}xM*-(fHP!7T0W!5yi$h=xT^90pd1g~FmtNuT^X zRaIs$ZCs#$cj$T7haKUXo5p$(#Fefe+#1&vzE8gI90_80XJK$xKVBE`xMEy{oKQ~} zj;m5bD~oCjnftD2x2-=_pTQ)b9qF2VKrB-@!vV#z36wr#x(X%|42X3!Tbl;y(L0QA zB;{El77v(}c2jrIpF!FOI#Hb@0y54KeS+9pMHCr5Z=f`E<)ON}Y$&E!kSvWK97&Ci z_2(yVou!BdwJOKFv4m)gppwN=5mS1JUI$h3{_wBLm)XlRqgY?0(i*RGNHUIXOAGy0 zKRI{sgE1n-wo9riTWOD6)6{1!_DTtMleuQgnM*Q>(I zg(RG@^bpj6KQxZsaA{u`z;|`-BSqOu4RUb_!c(Up{$cp_{~6vx?Ba?Hk4) zLNFlKsa)MTcn0){9cE`aoLK19*J_Mc|6mYic%ZY;ZObf0oFkol1LPI##vM4ai%3Lq ziJ(zk1cSQ;@CJBDIsWBJI_KlP%6ES|Q=e@s4`>u8`i1ieyF&$t2GuC0$j+U7xPI`p z+1OE(H_u%vAVV}x8XAn!C(MJ}FUp(Gxm;p^U8#rsICD#xF=zAenD=zp{iCxXN&lx zpQ9wI!->TOX5FYVW^`eYHZFjNQ^48i!r@COGDiFFQhO%g=mQr4nK&w1fMKj=`!6@0 zQN0h#R-Rqs%N;Bv#ffeKr%r9Xyw=Z1tfvmoIRQ*E^WkldwT71U$ph-XT=7tuTmr z+rRaLz|OjPvz+k^$nw%w6ic?hBLV!igb!GMQj1y8w(If-hPRU-`|%T+{% zGD!=>FCrailsJ@FST3+*YRZ(xT6<~z6%)9SyD=V&UK&W%J|FEb6N!PwgMPQLv0?~Qhu`%qWG zM=BjHyuT-E9wq}`MEZt%@;TYJYjH-StJO@#1tJavD7-EwzSr$wwEr*l`EsT zq_j0ACgAYoexdw$!UnMrzhBUN7vaghayn0U{P=a4s0%9WyFYRfDIv_e7&sfa>jbpo-UA|pg2P$rX6-Cz;KP5E zOV>Oun+V>R8yop^NRo1saF|FTo%!Psu;LXmNd&e|tB}fcZ^AUcFf1X29}b6%Mkh1{ ze^(wApx`RRd{T&oFOa5yWq&o1@_94UaT*v)6h3T^2u3fZF(q^UE2v7i$iyR zW1~w)6z0L_twJmW++PGX6WUoeCwOKS>-Q&yQ$w^Zw1K!J=^Eh5 zMQ_h0u`I=DQRWPQ;UBf0t|_fG+O_rkWE?4vOA6a$Um+HW;*v5bB!&`f5-YF5P_5D# zt2qS8hWKX$e2H-Zrs?)J*DE=ht2;Uv{*D%BY?-t@X2;zl}knP_X^Z9#QM4DLu_M{Tp}aC@4nqIjwI zB}nVbq^ww>(739;Ii_05WEYn~4P(#fD#I)yaB27S*3=p%l9i6e|Joo$>jda#>x9HOcIY`WBNUq zCEBz3+KibD(keupNMl=`{)Ma$cH=}dNqfFfmqybf^jMs>)OKSSP0Jc_`acjYa@m!x zYIe8}81ShEBZ1@0-0{2hs>?u%!t^5j&h@8NKd#tcclqDCu|I)L!2+r4Le zUr=2^{lfUz7*d)`cfLShqSP2KE5w|BK!oa&8slXk)6tY%-TnhtwI`2dYs!1J;J%6B z0+$Wb6+dz~ghOX3SpfMGrM1Shhp%buh8iNhxPIWx2dUe#D#W6RDKZ4?tUx$ycE~Q` ze1TSX;Wf{r9lf}*Y^>456!1Pd9Xz?QgbiZlmh0$c<7Dq^^@Q|bmiBb&O?`ezjh-HT z5BHm%Ac@*EQ?XhhCI_LvmKrYX$<*xGlc~Pt$a{45rNm=n z1pOv-6F$CqrhGJbjh@(l=!_{~xHj0SJnbK;H?(%&X!k(Aznhp7>u9f(9aTt}?5wDP z5qylz+1)W=d%EV==Ld}25t6-7 z#p?}(Q)6`?h3ht7P;X6UkakE%3Wp9ke7Fv41O0FXnZc2Q2M7kl+H_uhXjWfQ2G}~$ zY=16y@tFo-mL+e)wd4vb9{_Jea5mo!mYU7?&hm^cKeZf0vQ0BJB}b#)HP3x*awN5!?YVYFV6K~(bidc@y9Wt` zZ;p^G=;F&8viDinSc!mCKNKs9`}5+z`&u8prmnyKuGa-G7fz@%mMpS{fJf4QrK-P~ z&miriJ;a{trpl8wEU0JJh=r0zE+l(A94Cx{ujlQhUz?&@pWKnc1@L|BrM+K3UEB2l z!KfAZ$+=z^Z|H7R*Z)fo2}P2lqXoErdoZ5GPv0;;hAQ@3Uk|q1akze*BfClL0lnKg z4Sni|6RQ18i1l!sU>IED%|=;R9rF$^pBmFu@EE~>SpRpnzU0gzkMFzq@n2_iM+eiG zF^asa!?%k+C5!Xtb?g^T_39DG>jO4YcTXwDeflpo3Gj2SCEby0YE}9hp2tExxWQOT zbU1nkn}#^wL9Da+y5oa8Qb-{fYziE7v|D$F42bq!tPgP+uMsdx&xTQAkoGHK1n@f8gN-Zr!B< zG6=WG(VO?gLDOS!Bp=ts&9%-Wy-0sJ!jm^iPtc~Z^Bui-{Tkl~Hy>D#Brb&ff}A0< zP+BRq^r_|h>$frqn~aMQM1watu>QYs%M%2LLj+IhC42}Df3}x){i*t*z0bLS+CQ9H z#^#bgAhm*5KPEc~o@*$Oi;qM?$C41zKPi7Cl4(k4r?_BQFvz)ScI-voA$v6#YAk;$*JwDtLa` zmi?81oL+rHDO{-efc5n$xw`EWx(O$+ZD*PG+O9GF`N=-z4kpW=Kip01vwoWV2{ug* z)`?{v2j6?{05Q%~-)=Ac!K>=JHIo#Jo=On?rDSnGJtm~38y3}w?itKO)qH#LvJ>FN zUS8~(66DH_!ny%z+S^dvc#@&CY2Am@aO;gH8ROcdUwc zu26q4%!4-#rJByP4i)FYuOCz7b@Ai%1;f9myM)sb2q$K{4^+q}i33-)?|!(a_A-<7 zPDa8O#H<7J1CLV#r=o7+A zBCHnU5Aonriyx5U^fl9xr&vDl$a*~0SM&o)C?uORx^7SfR@NK$Ed0IptF$kfUGwq~ z$vj*ljP24nZHm1bq!!p4Z^nlTc7cIua|RD$cZT}biV5=B&kYps1eM5|k!cU6Rq@=p zgI{~VrAL@Mw@ueU}@K7_+bOw!}X&4e()bALRo@KcD$5v^^^w>HpN&n(t|Iqb{wPncda zAVMhF@r>dVkor!Wx#E0cfhq}5a3A|^hU;ICt*!ix$u;2K*FO*~oczmF`BqQ?iOw3g z)z%>3ndS+uoAhJ#i)l-kU$(S+}xkpC{?6aD^wBNvkDdbwf!Bi~m&&D2;&tej|4fiF7SMr{>hHFVz6es7PI ze%&pQdgy$CmeG<%TQ90gfL!ChnI!v7CgdcwqRr)0p!x$z^&^mKPU;OU zx;n<6dgA*h<=49f@LsBS!jNWX1LIU+Pu6cs)4VixO~tAVyIXDEiM3D27JIpH$2)uf z9t&FCg$CWvof0ov{my7v!CQaVhm0Og000>`Nklx13OIg9_U*>5NLUDz_)ZR&$v1g|?Qw9phHsHZ2lM2Q44(^854z$*3PrF!K~G_~Jla8rT#gz^z^| zypv!N*cGn-ZSgMLqlc#Hyq`|0-kkYM)w<2^wmgrI#>x^GCQE(+uUq2y61EI&f>;O~ z1i7ef6kp+q6F7@js^6-eR{Y ztU02@^`IX8@FCf-$9oE2_E&HRV_2fC#l6su;&Dh_c9~(znxpm0R_w1k`)3OcbPJY^ zlMMt?Z3I$zw*^um+7z)6W^%=>oG9DAAXz%`v;B1!?#ux%pLzc|+imvrKB5(1sEZs@SO=m_ z5(|ODQou&?1rfjHk6CX28UIbi*Y}g(mj~<4z`ry7!Ub=QiWUq??nsU4{vIy6u;*t$S0_dw$#;d3Dbo=&Q(f+j~U2`0_K^b{J4+4k_qy*njlD|Ky zi{L-48B(x?+r$E|q*bMhs!Q%Czhl`N+=9fkU0jbK zO4d7s-z7en-?xh&uaCc++gTwd{lWH;q1r;iVYM|!Hk3vjb7&TYAE?=?3MHLiSfx+B zbVGk4C0BRiWUekVqe!d9bre3hpMsk)D^U`)cfQ*ycL|xK(KmRLZ49On6t{^5FJLdx zX#QLq0x94U{%?+u%USR;tB#rT2)kIthiZi#O+E7vu^1Na?1?`c@>7z!b-g!XSvRH zL?Ny4WN)$0vjfEo!9Jrw$?tS<_Kt!9*RQ%wtbr@a?C$x}7LdA zHiu2;)uq3js&_PBSwWk%v6_SKe`)Wv7DA(Q&uF7lR*2>8 z?d?8bz{P)wRPtU5Ts8F(TKS#kI0OkHgAQ1GYX<9pE$dDgTojSFk zrlzJ%+te7AldHxmm_XIpLKunPypeOQnXKHX|>3buVNF*YG5DE^5`yPV7A zis*)bk6xuxRjAeKTkyZAuCA`h7akE2F$b<4Z7?)+*n#w#6crVP&cP4&{G?7m=gys9 z;`8}~^bL(tB$vydiHwXyBY7KJ_Z$%|4C(C4>jEBWn5bb7B7I4%@n%tt;p*9Z{pB-x z8oL{5vH@Q?C&W#{>5kzH?AXUW4~;EYNA50SL5p1tWEurTLUM^nl5E^d4%*FF2QNPt z((cX8&HW}nKmUq#0w7`-Z54Th^aNBG?!SyA6GMe{P$(3kaGy>tfD0Ecyi-|O+3He( zqNq^oa^o?SJB*EcefW@U%%eSov$s60=*Q!l2z5JHOPbvisMLZpi#-2Xe@3->#lE`J zY?Pxp0FewB*TXWA_uwwwMJ!MQxHP9f9vhps6_IE%9*-9THdKs-iSa!gss7e>w|L{?EI2@9r*(=_Tm+nf zN~?%DN(7#H3l)5&2KvrkfPGiBoBo?fg|KP7L!}cso$ho_PR?hb`t;C(Iq=d0K~fC` z;taJ&BxvDOKtRBYaCi{ELn}vez5{mF`|#fg63qIgG=4!ekgV0!Afmdu8UwE{fr@?= zY)D2%YD@6-MA;`&AeHrSVVr#f-}6#$>W^m@>kDN<4nNeL8yx7$?J5_M9e{9^+b(H# zKj!Gdv>f!MLM>ww)jrA@38VrKKtG{|-YpC@824c&L~j*FklGPdSv~Tyb&n&YL%H-W3FOb#;}C#o`X| z-{9xx2aiu0z?01cl9{A9*o7`2g@YkYIFJu*L{DgIm@lo>YHxumO)D!aI}GpOuFWq$ zn;=hqZjg9Jo#qZw>5k=S-+zCXa^?Oj>hj)J>0<*Z=_B2QNxeh(Ve3!Vm*P1u7apg7 z2Yz^zH!l`cWE7Av0N%a3xvt9cD-9Wkvb6g)oUU(nE4jG?r16R2{K0NwPNb2hrKmB~ zdb$uuoqE1NfA;V-ZB|Lm-Mjlh6$@8Cu6WYhh0_t8j4G9}>Uy~$*N!Zls|ArH!2 z_B--5pSuq4W(nAw-&sG?xL6@B#&O#bLP>QzNB3s(=j9(YwI2J$LM^UlbB8zDD}?W| zX{KT)+%_D|vc@Abuuo!Uf=GGtC68}stgl&=e)HdHb;%utu3tSWTlwXqE{}i}!m|!D zW62-vB0zOqJaKJxccRgfO?1N`%-|UbqIHD1|CBWMlP*XS7j2*Gwrtd@iqEXCVM44X zEqG|TeXNapsEh&(MKlzICgC@Zl^w;UZH!OBwASCP(}w_{qP9XM$66}9G0+ACVwv^_ z1d{p0$H#90lJ{vYJ-{8L>6;*Rqd_`70fJ85RJk#0OF2L~1wy$(ZRBY|DA=6e_UqS= z3#43wCm!i83Rpi)kz|>wMw&Vs=}T|#sQWCb1!HHjZyD+;>y4F2N~76ZBZ+@I%gvB< z59BZSb-EmXGIi5M`B40Q&$+JkkZ{H-tt6(UbIkOJq-l6~@zqj&vs*4VHPsF-^BvJy zumZ@X2XcL7Id79#W)# z%BO{6A>5CKN*DI;-+vu^kF_GHDLwpLYyR$n!~*qm1XL6Uq{`Pwsz&KS>eF>_EVB>; z|AP%U9wgBs`1+2$a>Mm^G0I%-hP6n|4h;h1V4#BE1qt|PUS3{ttUn)j*Sfv?1MbXq zu0XfxP?nnUMKVce;UFK^C*BLu*!E8~o{sA2Q)o~l|kosTPBx(G;bsets%EjM1k9od?@Kd#CQuM^(`*RpQ~4|z6}mVA*jfh(9qE3@KYrIj_E*hEesD2|0z2=JGUi;hIeo?BO_xP28yBa zxrT*>%>|qDF)PXJ<>Tv%T5dx}FK(E%>Om*_Y=Q1v-+Opj-=9$7B-C|a>}Tdx7|JW^ zX69!ZiE|DqVmpLc?KZq$jthW$eGX`Cfm1bM26SE^G6lxMZjY z*F7P`v^sR@ht1p&~ z^@-ubhflRm4|(UNrKLTdpPz5pH6kC#TG_E<$9?cjrV0Qe$iaqt5DpoxiFX@yMMXvX zYHDgsH}vuGxd18{m*vKN8chuko&oKZ4Pc+#u}D7-^@3J@Pn$x$y}jl@6Q@ynf0^^H zS0WhKk6Hqo;8liM?srFO6yhX7=auU$o^LUHb~SfTR3^B)?7WRtzs@tC8`?>jy5(s_ zBF5VAUgj;3P0x$JGwLgB9LKVjSa2)>mGx0VLc&lW!UT{$cGq)n1P*wG0#Idls0FAL ze~?zuR)#bIQfhT0Qr!kfE#ZIx1A15_21+ZXQn|&7R3@aSrymN`UuUmIYz5a(2 zO1m38;N!#uP(n+{;h#)(ZT|b-sqPXo&=TJT74L6J%p~0KPfkwGQg~TdG#Xx}9Mne~ zkl}GSjcLCIHDZB@3Z_(^M5~bGZQ2lsGIesn8zpFn*eg3e^t2>`j%%*sx zFKz7^k_}6rTdQ1L!yf-{Jf}H2J5hW@#3%aWd`nCM|GoN!Q7`rnr{-UO*K6M|r`4am zxvl1(mXxPbX)N)SatVv^Nt>S+!6ZOh2sTP%@w){K`fuXm;ts*FsSgk(3rLx9L<6zz zn^_oPTstE*fESB%oB$ubI4|w7dM@~P@qvw?IW4?(L>U;AjM1pT2z;2 zV_yL3bL8v8CBu)r;PJuZKUBS4SjlX`2c7ZmV7KLfi7ISTWP<^HBRM&FpIj~vw=(1d zL6V6;HMV3xT9Xd}q)?iFvs4KQ(m>W+CScU7pk9%9CMjojJnq^v2LaNo@y0}!z$z=F zQ!kh3F%*JjGcD4KV-=&kc@GVY5WWYVA){f|6T1szI{WZuTRb~X!-h;}4y0)|Un$WS z`pUSHF)@Pa;NkUYYAEDSDaxGAzSQvf14R=a>>`}$Bjfaka`1G92BJe=+{7^c;-zC; zQX%14d3bmT6B84=TO0YH!b}Uq+Aj9J<5-UFI(Tmv#e5>( zNLv=nB|3jR&IOlmdW}gQJ%f1tK+4>%FD{3?@$%N1Q9qxozpZA^zv9_Z;lu4?Y$nu} zQDmAezCO^6zhP7~)$6Ig;#r-2xs&yV1{H>3ioT(I+=^%O{Vq?>gjh}XnZ?A!j0QJ- zq;&;BWfql}m#e|+YPN_){)}BB-v9b`y^M=pW?= zkM$4;Z<|x3N$OQulgjc_Q}_3D;qfE_l9OAeGezRf9oqQ5Pn~+%qrR&j|5^0vkW6k8 zPB5l18t>1Xu^Y#N%1P+etJj+jsL10|}X?i*v6$He) z91sv-3bNsMbo;}?!hVE%tbu!=@r#|@UBO;k2%h3vKR>^Ci)RSs@dpQCfB*jdH^RLx z;7ANe9{}ll!}%TH0j>ce%_Yn#2d)!C-oN+l+jk9+=L$%xzCJ!aGa5C9Y*6bv?k=fg z@yR~rIv~nSP+>hSstRqK&OY3^>!!*lU3tqZt*p+7>s4^%Lel5R0=E#{jSOrkPHws3 zWO0o#%Q{jH5-;+fXWidfeYAcPNW&_MOLD)M;NsmB9Tp~w2hK?pf4qCX`@xgBx|Fmc zeO|fJsEYC9c`Qg0$65CJAsfnM8s!T$cwddoF0nvjbP5a%eAoJ%b-1CS5T=z<(!j`H zZTa3DfGn9HiB3SiiB_QjyvOanCL}2k>$@vguKeuflKllHg}|^L2di=oSy?K)nL#RTx2s zN0v0TMg#$>XT8%U)uD=T{~B_+jr z<>sc!Z9(Iav`slRHq)%wUzhrPl62(TBcV?zdg=@hhOm2v+VO@SyMu- zqtAS@SGhWoFgXVtQ(xr2pSzxyw2|g6Vu5XQ%skE=TZR~2_(m_tfZi8WS_v4~=SoXU zH|6E!ZAZdb5`IMBlMQ*B zmN5b~l?x;r5)~CS7i_G@fnXTk5EK8BKnK@U!ZjK28y{3HdRb3(@7{gCudnZX46?#^ zi8(!1Uk&7a3L3$usi~=V#BHFwID7I->W_Kl;tLsCy0t9@>$vJpoBeNNJ)0{N*Kohv zu=i+V&OZe=WbR_(+#r9kinpvp=JnJpEB)PP3Mx*! zeqHardFxl}OChiC)dz$bnS~jg_doWQFZb3}q18rHFM2%q;3z7vev4b`8M_%vf3H@a zvp+1$XJWt{2hYR*dW2U0I`S#IfaA+crMvS_f77zRH+7v^snhk>FHU8N&gH!{)om+B z<#Q9c&vDC_bsO?Z{5P5IJYj3ruePV#cXw-|LTwM3KdiCQ==yYEdWSx@ z)(5c(@2hfibF+X$T7i#W3oeK=YREVlwyF;}h_o*`t0pY$n&l63L7u;I4m0jONZh5Y zsCbukf{MDgmsegnKV#&F3dtMM_n9Nx4*dV;QRA_G<%$&^NBLd=D{JF|x49Qf-rd>B zJK>xDhUabvKC3VuymO%VOnch~d8tG1PMtcnd(FhArY4ac;gdpl_I{9A7T{7=_U*pI zvBzmfGmrH&`c}Bl(NR`@oV?@w^=aj-DmgOx#t)yA^4guB_0I9i_Jj-SDs6$azuP`8 zURcyJVfVY3#&<=hO7H$$Y`JR+a5bCgjfd%x6QA|wE@KV)w#UNdmg$wbw{`C{i?`Uc z3BA0O_v-Vi#vXC*uaUPehitc+yTi(SxA{}R-);7K8GX%vQ==Y;R_AhQ@s{(PsQLT;R=?w?y51J&Pb*7X zJ}ua}d84N^x0dMdjUS3Pt-Sx1(W9Mp$Lg(StQ!`eJG6hZ&n2eBz^i#C^>3JR)YlyF zebyh#U}$*lO3Kd0j*Kg}=Imo*aThY07h0?r-_84Q#?NBu<}>oq5&O3DJ@#FB?YY}c z4}KSC>muvKt?4Rkf7E8#_ZIGR$o^glTwI&*J!*1n&i_*5g||Q6Z=_(aA=Yh)AO15; YUAVF_e#6;Sz&(}>p00i_>zopr0BtwTEC2ui literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable/bitcoins.png b/app/src/main/res/drawable/bitcoins.png new file mode 100644 index 0000000000000000000000000000000000000000..6d37edc11ea060cc02467d3d2c73eddf5052c844 GIT binary patch literal 12374 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 IM6N<$f`ro^V*mgE literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable/bitcoins_testnet.png b/app/src/main/res/drawable/bitcoins_testnet.png new file mode 100644 index 0000000000000000000000000000000000000000..8c91d1baad1c080355d4a7c2193cb8b6a868d02a GIT binary patch literal 20321 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 + + + + diff --git a/app/src/main/res/drawable/card_bg.png b/app/src/main/res/drawable/card_bg.png new file mode 100644 index 0000000000000000000000000000000000000000..72b4041941ac45a936f132f2b5542955bb85f6e1 GIT binary patch literal 94143 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

cCO8hFY511AO5=mM5D6y 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

pSm6Z@ASo2Dx4ZitqK7&|^UHeXbsr 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 Hu0mjfd=Cm| literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable/ert_token.png b/app/src/main/res/drawable/ert_token.png new file mode 100644 index 0000000000000000000000000000000000000000..adbe18e652fef36cc32a03d49ab3338ba20d4f80 GIT binary patch literal 22141 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable/hand_card.png b/app/src/main/res/drawable/hand_card.png new file mode 100644 index 0000000000000000000000000000000000000000..08032733f9f62835bc3c5599b9f19162c47c8f9f GIT binary patch literal 15692 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 a%jE}8ijd`^5VI=)0000 + + + diff --git a/app/src/main/res/drawable/ic_developer_version.png b/app/src/main/res/drawable/ic_developer_version.png new file mode 100644 index 0000000000000000000000000000000000000000..072e1479c5c442994621ccf3d45d819629b25cd8 GIT binary patch literal 3319 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 BM-%`6 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable/ic_fp_40px.png b/app/src/main/res/drawable/ic_fp_40px.png new file mode 100644 index 0000000000000000000000000000000000000000..122f44257b1aa188955ab01e5bf71a5f079b1da5 GIT binary patch literal 4001 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable/ic_info.png b/app/src/main/res/drawable/ic_info.png new file mode 100644 index 0000000000000000000000000000000000000000..042ee753975076bbb6cdcfb0d2ddfcc5586cb236 GIT binary patch literal 6701 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 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_launcher_foreground.xml b/app/src/main/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 0000000000..7522eb07a9 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_local_atm_black_24dp.xml b/app/src/main/res/drawable/ic_local_atm_black_24dp.xml new file mode 100644 index 0000000000..1b86138060 --- /dev/null +++ b/app/src/main/res/drawable/ic_local_atm_black_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_logo_small.png b/app/src/main/res/drawable/ic_logo_small.png new file mode 100644 index 0000000000000000000000000000000000000000..1bedd8792ae7f94dc1a7bf6451914dab1a6dd1f4 GIT binary patch literal 6970 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 M07*qoM6N<$f>LO5k^lez literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable/ic_search_gray.png b/app/src/main/res/drawable/ic_search_gray.png new file mode 100644 index 0000000000000000000000000000000000000000..88be1d6fc4b142ba97def25e7ddb6dbb242e68ac GIT binary patch literal 734 zcmV<40wMj0P)004R> z004l5008;`004mK004C`008P>0026e000+ooVrmw00002VoOIv0RM-N%)bBt010qN zS#tmY3ljhU3ljkVnw%H_00LG?L_t(o!|m8RNLx`D!12UnDwW(m0$p@!rcN0$iYP9E zg9uW)3PxPq{zJh^gG0NxIk>ii8AK3VoJ29S^gX#bSg1(rq)ii2qUn(1=ecQe@42r` z;Ven+_ub!pd^xWevF^2459?vUu{eX|7$QqLO3Y$`UCK11ow30r$*{40Uh`XH^mf85 z@$j$_esq9t+93nsVDr>ns0Izr$py#eZKwRhCW}lk$`niNbFCe_AR7!D(eA2B0juSB zhN8xunpbGMvbcR$$2{qfI88;Ow)|tSH0qH<>M&J_9P@`+QtZ`^cXiY9f*tt77UTp4 zgY=R_XZ&EhD!KAsS+r~8g5e#DOUJZn^k5R4tN0!7*q~hLqG5$?OEG!Ia)M15Vyh~? z>>V2tY}622QSn#av8-TIhS-{lzx0l!1zR%2_Eh|lpVB{5$@@msUFk)Ney|Odd~NhM zz3oHh{9u!k?tZOz_^x7WJoJMlIT3E!C3#&U-}vqIXXO+Xh8vi zE;*veoc0&C_@r@Pow?u(ArD*_>n@9rHtt@ui$Od0r4tv{s?yGVZqJ3ch)uO{R literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable/ic_show_menu.png b/app/src/main/res/drawable/ic_show_menu.png new file mode 100644 index 0000000000000000000000000000000000000000..b4bd987bcbd4b62cd2c4b405bb091e67ba7a2bee GIT binary patch literal 3068 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable/lock_pin.png b/app/src/main/res/drawable/lock_pin.png new file mode 100644 index 0000000000000000000000000000000000000000..af1a616a4dcd6effaec7d21b5f67dbfbb592e272 GIT binary patch literal 4251 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable/lock_pin2.png b/app/src/main/res/drawable/lock_pin2.png new file mode 100644 index 0000000000000000000000000000000000000000..4befee5537265df28bfe25ccfc15782dbe02bd9e GIT binary patch literal 4915 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable/logo.png b/app/src/main/res/drawable/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..35f101bb12ec9606ee78aeaef0b3164302c6b701 GIT binary patch literal 61543 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable/nfc_scan.png b/app/src/main/res/drawable/nfc_scan.png new file mode 100644 index 0000000000000000000000000000000000000000..9968bb221b16ea251c43c5baf8f558d086b901dd GIT binary patch literal 29516 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 X4b`t#l1Y(A0X|AE)#Zz2O#=QG-%rE( literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable/parsed_msg_bg.xml b/app/src/main/res/drawable/parsed_msg_bg.xml new file mode 100644 index 0000000000..ad2e4a821e --- /dev/null +++ b/app/src/main/res/drawable/parsed_msg_bg.xml @@ -0,0 +1,13 @@ + + + + + diff --git a/app/src/main/res/drawable/parsed_msg_bg_pressed.xml b/app/src/main/res/drawable/parsed_msg_bg_pressed.xml new file mode 100644 index 0000000000..47b21d4368 --- /dev/null +++ b/app/src/main/res/drawable/parsed_msg_bg_pressed.xml @@ -0,0 +1,6 @@ + + + + + diff --git a/app/src/main/res/drawable/parsed_msg_bg_states.xml b/app/src/main/res/drawable/parsed_msg_bg_states.xml new file mode 100644 index 0000000000..91aff98446 --- /dev/null +++ b/app/src/main/res/drawable/parsed_msg_bg_states.xml @@ -0,0 +1,8 @@ + + + + + + diff --git a/app/src/main/res/drawable/qr_scan.png b/app/src/main/res/drawable/qr_scan.png new file mode 100644 index 0000000000000000000000000000000000000000..9755d27edfd0629206963b1ef44365166b8ed279 GIT binary patch literal 7035 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

l;a3?rouTm?jkncZUOkq=!-I6NGF{w%`(L0OBoB z>G6Qlb#V@G2*>3bdkId21`9dkbp_YPcOk8_kx$rGOvMd{5RSt|eEfs;X+7c85{y=+ z)6zHrt(9IGQI+H*++T?FqaGU^1L+N6(XOF6F9mSbywG{ z;2x%C?-B(IJR1+>nf(zu>vH(SaL z)gh2%9u09;nMtk{!F0)K2Fa&_O_)h$8F&EqNRa$TT>u_mqz+6uS;(W9s8}%Ru#*s+xWhGcGtzJ8Cx-twrzE-A zL^7U}CdoZT{LTkz%+}c)h;D;C3Fh__+mBq(dn8dwpp+PN#G8*wWcSY2O#ACcgf%Dsw1UBBZhY4{E%R%P%0ATC+Zu&tXF{4{S*~-GCEJF891RI^;&MdINLW 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable/tangem2.png b/app/src/main/res/drawable/tangem2.png new file mode 100644 index 0000000000000000000000000000000000000000..63e132148508b00fc4abcbd05332145fdb9c6a17 GIT binary patch literal 3214 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 Szw^&J0ET*|kiT`1QU3yZcl?+D literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable/tangem_logo_full_new.png b/app/src/main/res/drawable/tangem_logo_full_new.png new file mode 100644 index 0000000000000000000000000000000000000000..ffd6d294eab89ae7b58a8290f749ace94c55581a GIT binary patch literal 8440 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 b)(dG=2`;*WJr+GwJ_bmOD~Q#I7zX_h{k4v_ literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable/tangem_logo_icon_new.png b/app/src/main/res/drawable/tangem_logo_icon_new.png new file mode 100644 index 0000000000000000000000000000000000000000..63e132148508b00fc4abcbd05332145fdb9c6a17 GIT binary patch literal 3214 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 Szw^&J0ET*|kiT`1QU3yZcl?+D literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable/tangem_logo_small_new.png b/app/src/main/res/drawable/tangem_logo_small_new.png new file mode 100644 index 0000000000000000000000000000000000000000..e3d59e92b736aa8d9251f6073c867ec48532bcda GIT binary patch literal 3109 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

@l{DVS2l3q;I1Q*H{$h<6-G12pk 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

Jo^3;aAf~P;_whw+8(Ye|R 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable/timer.png b/app/src/main/res/drawable/timer.png new file mode 100644 index 0000000000000000000000000000000000000000..a971c810b7ed9c51e6ca09b5ee54c19624e62325 GIT binary patch literal 5448 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable/unnamed.png b/app/src/main/res/drawable/unnamed.png new file mode 100644 index 0000000000000000000000000000000000000000..7bba27850d000ef78decd25c381fc10d5fc0d857 GIT binary patch literal 13354 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