From 893db178db4605a83eeb37848c576f106ee1ca26 Mon Sep 17 00:00:00 2001 From: Mark Lundeberg Date: Mon, 31 Aug 2020 17:41:39 +0900 Subject: [PATCH 001/208] [fusion] Increase 'fuzz fee' to be tier/10^6 Since the server is now going up to 8 BCH tier level, it makes sense to start paying closer attention to this. --- electroncash_plugins/fusion/fusion.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/electroncash_plugins/fusion/fusion.py b/electroncash_plugins/fusion/fusion.py index e493e369a043..322554ce7321 100644 --- a/electroncash_plugins/fusion/fusion.py +++ b/electroncash_plugins/fusion/fusion.py @@ -598,9 +598,9 @@ def allocate_outputs(self,): # linkage somehow, which means throwing away some sats as extra fees beyond # the minimum requirement. - # For now, just throw on a few unobtrusive extra sats at the higher tiers, at most 9. - # TODO: smarter selection for high tiers (how much will users be comfortable paying?) - fuzz_fee_max = min(9, scale // 1000000) + # Just use (tier / 10^6) as fuzzing range. For a 10 BCH tier this means + # randomly overpaying fees of 0 to 1000 sats. + fuzz_fee_max = scale // 1000000 ### End fuzzing fee range selection ### @@ -614,9 +614,6 @@ def allocate_outputs(self,): fuzz_fee = secrets.randbelow(fuzz_fee_max_reduced + 1) assert fuzz_fee <= fuzz_fee_max_reduced and fuzz_fee_max_reduced <= fuzz_fee_max - # TODO: this can be removed when the above is updated - assert fuzz_fee < 100, 'sanity check: example fuzz fee should be small' - reduced_avail_for_outputs = avail_for_outputs - fuzz_fee if reduced_avail_for_outputs < offset_per_output: continue From 826eda32fc81581c52108b89a1280e3bb3417a5d Mon Sep 17 00:00:00 2001 From: SomberNight Date: Wed, 10 Jul 2019 20:26:25 +0200 Subject: [PATCH 002/208] appimage build: build was failing on some host systems On Ubuntu host, build succeeded; but e.g. on Manjaro host, it failed with: ``` ./build.sh: line 233: /opt/electrum/contrib/build-linux/appimage/../../../contrib/build-linux/appimage/.cache/appimage/appimagetool: No such file or directory ``` This seems to be caused by applications like `AppImageLauncher` registering the AppImage magic bytes with `binfmt_misc` leaking into the Docker container. See also: https://github.com/AppImage/pkg2appimage/issues/373 --- contrib/build-linux/appimage/_build.sh | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/contrib/build-linux/appimage/_build.sh b/contrib/build-linux/appimage/_build.sh index 0fe23b3ad358..e794e4d237b3 100755 --- a/contrib/build-linux/appimage/_build.sh +++ b/contrib/build-linux/appimage/_build.sh @@ -206,8 +206,11 @@ find -exec touch -h -d '2000-11-11T11:11:11+00:00' {} + info "Creating the AppImage" ( cd "$BUILDDIR" - chmod +x "$CACHEDIR/appimagetool" - "$CACHEDIR/appimagetool" --appimage-extract + cp "$CACHEDIR/appimagetool" "$CACHEDIR/appimagetool_copy" + # zero out "appimage" magic bytes, as on some systems they confuse the linker + sed -i 's|AI\x02|\x00\x00\x00|' "$CACHEDIR/appimagetool_copy" + chmod +x "$CACHEDIR/appimagetool_copy" + "$CACHEDIR/appimagetool_copy" --appimage-extract # We build a small wrapper for mksquashfs that removes the -mkfs-fixed-time option # that mksquashfs from squashfskit does not support. It is not needed for squashfskit. cat > ./squashfs-root/usr/lib/appimagekit/mksquashfs << EOF From 977b23c4a0a6df82db6caaa1663106ebde9930f2 Mon Sep 17 00:00:00 2001 From: Calin Culianu Date: Thu, 4 Mar 2021 17:45:42 +0200 Subject: [PATCH 003/208] Updated releases.json for 4.2.4 --- contrib/update_checker/releases.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contrib/update_checker/releases.json b/contrib/update_checker/releases.json index 4ef4659cc5e8..f6b40d727aaa 100644 --- a/contrib/update_checker/releases.json +++ b/contrib/update_checker/releases.json @@ -1,6 +1,6 @@ { - "4.2.3": { - "bitcoincash:qphax4cg8sxuc0qnzk6sx25939ma7y877uz04s2z82": "G4Ows5ROkK1hkChmyoj19sj+subfPQ8WeQ/7LNBJuMP+O5vPwfV007cIU9/qXsO3i3QWvp9qHSWQaJgyr1FGhuE=" + "4.2.4": { + "bitcoincash:qphax4cg8sxuc0qnzk6sx25939ma7y877uz04s2z82": "HEKN2H+NZmvWzcXGUrT8oaDPT/bbOhoCiuzJ5maBUfmDIBIOxIZFdFmdPkXoDMDesKkZdCFm1hG6bB0HsGhgR0U=" }, "3.3.4CS": { "bitcoincash:qphax4cg8sxuc0qnzk6sx25939ma7y877uz04s2z82": "HNkxAJzvWGP5/YPIJrZRQFK5btM1nd/NKCwWAlejc5oEJ6VNbzo6KfqOoIBpPTauK21Tp/qXY7SMmlO+6ssMPOA=" From 6a5d60694a8f15a227f40b809b9f4b8162eb435c Mon Sep 17 00:00:00 2001 From: Calin Culianu Date: Fri, 5 Mar 2021 11:37:55 +0200 Subject: [PATCH 004/208] Requirements: Restring PyQt5 version to >=5.12.3 and < 5.15.3 PyQt5 5.15.3 no longer ships binaries due to Qt's commercial license shenanigans. This means if you install PyQt5 5.15.3, it just doesn't work since the binaries are missing. PyQt5 5.15.2 does not have this problem, so we are stuck on 5.15.2 max version for now. --- contrib/requirements/requirements-binaries.txt | 2 +- setup.py | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/contrib/requirements/requirements-binaries.txt b/contrib/requirements/requirements-binaries.txt index 2ebc9d0d71c8..3ef592bc4bf9 100644 --- a/contrib/requirements/requirements-binaries.txt +++ b/contrib/requirements/requirements-binaries.txt @@ -1,4 +1,4 @@ -PyQt5>=5.12.3 +PyQt5<5.15.3,>=5.12.3 pycryptodomex psutil diff --git a/setup.py b/setup.py index 48f2189295d0..919d2bb78024 100755 --- a/setup.py +++ b/setup.py @@ -16,6 +16,9 @@ with open('contrib/requirements/requirements-hw.txt') as f: requirements_hw = f.read().splitlines() +with open('contrib/requirements/requirements-binaries.txt') as f: + requirements_binaries = f.read().splitlines() + version = imp.load_source('version', 'electroncash/version.py') if sys.version_info[:3] < (3, 6): @@ -145,8 +148,8 @@ def run(self): install_requires=requirements, extras_require={ 'hardware': requirements_hw, - 'gui': ['pyqt5'], - 'all': requirements_hw + ['pyqt5'] + 'gui': requirements_binaries, + 'all': requirements_hw + requirements_binaries }, packages=[ 'electroncash', From 65ac83e413a3cab9b54a26afbf366da4ba652655 Mon Sep 17 00:00:00 2001 From: Calin Culianu Date: Fri, 5 Mar 2021 14:17:00 +0200 Subject: [PATCH 005/208] Re-enabled PyQt 5.15.3 Turns out that just pulls in 5.15.2 (Lol). It's fine. It works. --- contrib/requirements/requirements-binaries.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/requirements/requirements-binaries.txt b/contrib/requirements/requirements-binaries.txt index 3ef592bc4bf9..2ebc9d0d71c8 100644 --- a/contrib/requirements/requirements-binaries.txt +++ b/contrib/requirements/requirements-binaries.txt @@ -1,4 +1,4 @@ -PyQt5<5.15.3,>=5.12.3 +PyQt5>=5.12.3 pycryptodomex psutil From 4454cb4c91455cf64036c15d7b179dd0e9c2c426 Mon Sep 17 00:00:00 2001 From: Calin Culianu Date: Fri, 5 Mar 2021 14:32:34 +0200 Subject: [PATCH 006/208] OSX Fix: Allow for Mojave+ dark mode to work (requires Qt 5.15.2) We add an Info.plist key so that this works again on mac. --- contrib/osx/make_osx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/contrib/osx/make_osx b/contrib/osx/make_osx index 997ff7e6b080..449365d8abb5 100755 --- a/contrib/osx/make_osx +++ b/contrib/osx/make_osx @@ -227,7 +227,8 @@ plutil -insert 'NSCameraUsageDescription' \ || fail "Could not add keys to Info.plist. Make sure the program 'plutil' exists and is installed." -FORCE_MOJAVE_DARK=0 # Set to 1 to try and add the Info.plist key to force mojave dark mode supprt. As of PyQt 5.14.1, it doesn't work. +FORCE_MOJAVE_DARK=1 # Set to 1 to try and add the Info.plist key to force mojave dark mode support. + # On PyQt 5.14.1, it doesn't work, but on 5.15.2 it does. if ((DARWIN_VER >= 18 && FORCE_MOJAVE_DARK)); then # Add a key to Info.plist key to support Mojave dark mode info "Adding Mojave dark mode support to Info.plist" From 8a1edc74c60ba043a5c500119baaaf0010ea139f Mon Sep 17 00:00:00 2001 From: Axel Gembe Date: Sat, 6 Mar 2021 16:07:13 +0700 Subject: [PATCH 007/208] NSIS: Wait for the uninstaller to finish When there is an existing installation and the user chose to uninstall, wait for the uninstaller to finish. Normal ExecWait doesn't work because by default the uninstaller spawns a new process. This also exits the installation if the uninstall fails. --- contrib/build-wine/electron-cash.nsi | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/contrib/build-wine/electron-cash.nsi b/contrib/build-wine/electron-cash.nsi index 0adcb46892c8..36ee900761c4 100644 --- a/contrib/build-wine/electron-cash.nsi +++ b/contrib/build-wine/electron-cash.nsi @@ -114,7 +114,10 @@ Function .onInit ReadRegStr $R1 ${INSTDIR_REG_ROOT} "${INSTDIR_REG_KEY}" DisplayName ${If} $R0 != "" ${AndIf} ${Cmd} ${|} MessageBox MB_YESNO|MB_ICONEXCLAMATION "$R1 has already been installed. $\nDo you want to remove the previous version before installing $(^Name)?" /SD IDNO IDYES ${|} - ExecWait $R0 + GetFullPathName $R1 "$R0\.." + ExecWait '$R0 _?=$R1' + IfErrors 0 +2 + Abort ${EndIf} ; Check for administrator rights From 38f68b0af8a3f9a27c812e4452927bf72d9faab2 Mon Sep 17 00:00:00 2001 From: Axel Gembe Date: Sat, 6 Mar 2021 16:08:29 +0700 Subject: [PATCH 008/208] NSIS: Ensure the process is not running when (un)installing Installing when the process is running can have unintended effects: See https://github.com/spesmilo/electrum/issues/6748 To address this we disallow (un)installing as long as the process is running. To detect that the process is we try to open the .exe file with write access (append, so we don't truncate the file) and if that fails we know the process is running. --- contrib/build-wine/electron-cash.nsi | 37 ++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/contrib/build-wine/electron-cash.nsi b/contrib/build-wine/electron-cash.nsi index 0adcb46892c8..9b378e3b5ee5 100644 --- a/contrib/build-wine/electron-cash.nsi +++ b/contrib/build-wine/electron-cash.nsi @@ -102,6 +102,31 @@ !insertmacro MUI_LANGUAGE "English" +;-------------------------------- +;Functions + +!macro CreateEnsureNotRunning prefix operation + +Function ${prefix}EnsureNotRunning + Pop $R0 + IfFileExists "$R0\${INTERNAL_NAME}.exe" 0 noexe + ; Check if we can append to the .exe file. If we can't that means it is still running. + retryopen: + FileOpen $0 "$R0\${INTERNAL_NAME}.exe" a + IfErrors 0 closeexe + MessageBox MB_RETRYCANCEL "Can not ${operation} because ${PRODUCT_NAME} is still running. Close it and retry." /SD IDCANCEL IDRETRY retryopen + Abort + closeexe: + FileClose $0 + noexe: +FunctionEnd + +!macroend + +; The function has to be created twice, once for the installer and once for the uninstaller +!insertmacro CreateEnsureNotRunning "" "install" +!insertmacro CreateEnsureNotRunning "un." "uninstall" + ;-------------------------------- ;Installer Sections @@ -109,6 +134,14 @@ Function .onInit !insertmacro UNINSTALL.LOG_PREPARE_INSTALL + ; Check if already installed and ensure the process is not running if it is + ReadRegStr $R0 ${INSTDIR_REG_ROOT} "${INSTDIR_REG_KEY}" "UninstallDirectory" + IfErrors noinstdir 0 + Push $R0 + Call EnsureNotRunning + noinstdir: + ClearErrors + ; Request uninstallation of an old Electron Cash installation ReadRegStr $R0 ${INSTDIR_REG_ROOT} "${INSTDIR_REG_KEY}" UninstallString ReadRegStr $R1 ${INSTDIR_REG_ROOT} "${INSTDIR_REG_KEY}" DisplayName @@ -212,5 +245,9 @@ Section "Uninstall" SectionEnd Function UN.onInit + ; Ensure the process is not running in the uninstallation directory + Push $INSTDIR + Call un.EnsureNotRunning + !insertmacro UNINSTALL.LOG_BEGIN_UNINSTALL FunctionEnd From bd9417154d264afe232131e42402a3c6e24fe940 Mon Sep 17 00:00:00 2001 From: Axel Gembe Date: Sun, 7 Mar 2021 17:43:47 +0700 Subject: [PATCH 009/208] Servers: Add electroncash.de scalenet server --- electroncash/servers_scalenet.json | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/electroncash/servers_scalenet.json b/electroncash/servers_scalenet.json index 3ee119074492..0c707c0e7ded 100644 --- a/electroncash/servers_scalenet.json +++ b/electroncash/servers_scalenet.json @@ -11,5 +11,14 @@ "s": "63002", "t": "63001", "display": "sbch.loping.net" + }, + "electroncash.de": { + "t": "55003", + "s": "55004" + }, + "jktsologn7uprtwn7gsgmwuddj6rxsqmwc2vaug7jwcwzm2bxqnfpwad.onion": { + "s": "55003", + "t": "55004", + "display": "electroncash.de" } } From e31d062d7c6d60c884f5defdcc1ccef9dbd543d4 Mon Sep 17 00:00:00 2001 From: Axel Gembe Date: Sun, 7 Mar 2021 17:47:43 +0700 Subject: [PATCH 010/208] Servers: Fix indentation --- electroncash/servers_testnet4.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/electroncash/servers_testnet4.json b/electroncash/servers_testnet4.json index 859301522033..58ebd946bfc9 100644 --- a/electroncash/servers_testnet4.json +++ b/electroncash/servers_testnet4.json @@ -12,7 +12,7 @@ "t": "62001", "display": "tbch4.loping.net" }, - "electroncash.de": { + "electroncash.de": { "t": "54003", "s": "54004" }, From f59260435c470386225b1ec7b4318dfbb83e2233 Mon Sep 17 00:00:00 2001 From: Axel Gembe Date: Thu, 11 Mar 2021 16:50:42 +0700 Subject: [PATCH 011/208] Build: Use git version with patch for CVE-2021-21300 https://nvd.nist.gov/vuln/detail/CVE-2021-21300 https://launchpad.net/ubuntu/+source/git/1:2.17.1-1ubuntu0.8 --- contrib/build-linux/appimage/Dockerfile_ub1804 | 2 +- contrib/build-wine/docker/Dockerfile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/contrib/build-linux/appimage/Dockerfile_ub1804 b/contrib/build-linux/appimage/Dockerfile_ub1804 index 79fb4508f0f7..06618cdf79c2 100644 --- a/contrib/build-linux/appimage/Dockerfile_ub1804 +++ b/contrib/build-linux/appimage/Dockerfile_ub1804 @@ -10,7 +10,7 @@ RUN echo deb ${UBUNTU_MIRROR} bionic main restricted universe multiverse > /etc/ echo deb ${UBUNTU_MIRROR} bionic-security main restricted universe multiverse >> /etc/apt/sources.list && \ apt-get update -q && \ apt-get install -qy \ - git=1:2.17.1-1ubuntu0.7 \ + git=1:2.17.1-1ubuntu0.8 \ wget=1.19.4-1ubuntu2.2 \ make=4.1-9.1ubuntu1 \ autotools-dev=20180224.1 \ diff --git a/contrib/build-wine/docker/Dockerfile b/contrib/build-wine/docker/Dockerfile index 1f60ec4fca09..868c5d4acb80 100644 --- a/contrib/build-wine/docker/Dockerfile +++ b/contrib/build-wine/docker/Dockerfile @@ -33,7 +33,7 @@ RUN echo deb ${UBUNTU_MIRROR} bionic main restricted universe multiverse > /etc/ apt-get update -q && \ apt-get install -qy \ wget=1.19.4-1ubuntu2.2 \ - git=1:2.17.1-1ubuntu0.7 \ + git=1:2.17.1-1ubuntu0.8 \ p7zip-full=16.02+dfsg-6 \ make=4.1-9.1ubuntu1 \ autotools-dev=20180224.1 \ From 29b8b8654768b557eb29b92f04731c379cc41b21 Mon Sep 17 00:00:00 2001 From: Axel Gembe Date: Thu, 11 Mar 2021 19:14:17 +0700 Subject: [PATCH 012/208] setup.py: Fix typo See https://github.com/Electron-Cash/Electron-Cash/commit/15968daf7613eaea7ce7ee0668582cc2d835efb8#r47889408 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 919d2bb78024..d61dd6517c13 100755 --- a/setup.py +++ b/setup.py @@ -58,7 +58,7 @@ # Menu icon (os.path.join(share_dir, 'icons/hicolor/256x256/apps/'), ['icons/electron-cash.png']), (os.path.join(share_dir, 'pixmaps/'), ['icons/electron-cash.png']), - (os.path.join(share_dir, 'icons/hicolor/scaleable/apps.'), ['icons/electron-cash.svg']), + (os.path.join(share_dir, 'icons/hicolor/scaleable/apps/'), ['icons/electron-cash.svg']), # Menu entry (os.path.join(share_dir, 'applications/'), ['electron-cash.desktop']), # App stream (store) metadata From 380f04a8054fe9fd9169721089c5da360f9d0807 Mon Sep 17 00:00:00 2001 From: Axel Gembe Date: Tue, 16 Mar 2021 12:38:38 +0700 Subject: [PATCH 013/208] AppImage: Include libxcb into the image Ubuntu 20.04 needs `libxcb-xinerama.so.0` and Debian 10 needs `libxcb-util.so.1` for the Qt `xcb` plugin. See #2189 Closes #2196 --- contrib/build-linux/appimage/Dockerfile_ub1804 | 2 ++ contrib/build-linux/appimage/_build.sh | 3 +++ 2 files changed, 5 insertions(+) diff --git a/contrib/build-linux/appimage/Dockerfile_ub1804 b/contrib/build-linux/appimage/Dockerfile_ub1804 index 06618cdf79c2..b03ba0c1efb8 100644 --- a/contrib/build-linux/appimage/Dockerfile_ub1804 +++ b/contrib/build-linux/appimage/Dockerfile_ub1804 @@ -28,6 +28,8 @@ RUN echo deb ${UBUNTU_MIRROR} bionic main restricted universe multiverse > /etc/ libpcsclite-dev=1.8.23-1 \ swig=3.0.12-1 \ libxkbcommon-x11-0=0.8.2-1~ubuntu18.04.1 \ + libxcb-util1=0.4.0-0ubuntu3 \ + libxcb-xinerama0=1.13-2~ubuntu18.04 \ autopoint=0.19.8.1-6ubuntu0.3 \ zlib1g-dev=1:1.2.11.dfsg-0ubuntu2 \ libfreetype6=2.8.1-2ubuntu2.1 \ diff --git a/contrib/build-linux/appimage/_build.sh b/contrib/build-linux/appimage/_build.sh index e794e4d237b3..f5a0b1b46c5f 100755 --- a/contrib/build-linux/appimage/_build.sh +++ b/contrib/build-linux/appimage/_build.sh @@ -153,6 +153,9 @@ cp -fp /usr/lib/x86_64-linux-gnu/libusb-1.0.so "$APPDIR"/usr/lib/x86_64-linux-gn # some distros lack libxkbcommon-x11 cp -f /usr/lib/x86_64-linux-gnu/libxkbcommon-x11.so.0 "$APPDIR"/usr/lib/x86_64-linux-gnu || fail "Could not copy libxkbcommon-x11" +# some distros lack some libxcb libraries (see #2189, #2196) +cp -f /usr/lib/x86_64-linux-gnu/libxcb-* "$APPDIR"/usr/lib/x86_64-linux-gnu || fail "Could not copy libxkcb" + info "Stripping binaries of debug symbols" # "-R .note.gnu.build-id" also strips the build id # "-R .comment" also strips the GCC version information From 97ce55f670a90e1a40bd6bfbe0ccd5f54b660eb8 Mon Sep 17 00:00:00 2001 From: Jonas Lundqvist Date: Thu, 18 Mar 2021 11:12:29 +0100 Subject: [PATCH 014/208] Add requirements-binaries.txt to manifest This fixes tox. --- MANIFEST.in | 1 + 1 file changed, 1 insertion(+) diff --git a/MANIFEST.in b/MANIFEST.in index 9b1c3f2162d1..781d3ad2b0e7 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -6,6 +6,7 @@ include *.py include electron-cash include contrib/requirements/requirements.txt include contrib/requirements/requirements-hw.txt +include contrib/requirements/requirements-binaries.txt recursive-include electroncash *.py recursive-include electroncash_gui *.py recursive-include electroncash_plugins *.py From 23728998da1a4d610d02b156bfdab63526a5ac54 Mon Sep 17 00:00:00 2001 From: Jonas Lundqvist Date: Sun, 21 Mar 2021 00:20:28 +0100 Subject: [PATCH 015/208] Install Wizard: Add derivation path scanner (#2199) * Install Wizard: Add derivation path scanner This allows a user to scan a set of known derivation paths for transactions when restoring a wallet from a seed. * Use global network instance * Do not touch GUI stuff from other thread The callback will not emit a signal and the table update will be done in the main thread. Also add early return if there isn't any network so we don't have to mess around with any keys, wallets or stuff like that if we can't really scan. --- electroncash/base_wizard.py | 7 +- electroncash_gui/qt/installwizard.py | 141 +++++++++++++++++++++++++++ 2 files changed, 145 insertions(+), 3 deletions(-) diff --git a/electroncash/base_wizard.py b/electroncash/base_wizard.py index ed38b3a733bb..03da7d015f74 100644 --- a/electroncash/base_wizard.py +++ b/electroncash/base_wizard.py @@ -272,7 +272,7 @@ def on_device(self, name, device_info): default_derivation = keystore.bip44_derivation_145(0) self.derivation_dialog(f, default_derivation) - def derivation_dialog(self, f, default_derivation): + def derivation_dialog(self, f, default_derivation, seed=''): message = '\n'.join([ _('Enter your wallet derivation here.'), _('If you are not sure what this is, leave this field unchanged.'), @@ -280,7 +280,8 @@ def derivation_dialog(self, f, default_derivation): _("If you want the wallet to use Bitcoin Cash addresses use m/44'/145'/0'"), _("The placeholder value of {} is the default derivation for {} wallets.").format(default_derivation, self.wallet_type), ]) - self.line_dialog(run_next=f, title=_('Derivation for {} wallet').format(self.wallet_type), message=message, default=default_derivation, test=bitcoin.is_bip32_derivation) + scannable = True if self.wallet_type == 'standard' else False + self.derivation_path_dialog(run_next=f, title=_('Derivation for {} wallet').format(self.wallet_type), message=message, default=default_derivation, test=bitcoin.is_bip32_derivation, seed=seed, scannable=scannable) def on_hw_derivation(self, name, device_info, derivation): from .keystore import hardware_keystore @@ -334,7 +335,7 @@ def on_restore_seed(self, seed, is_bip39, is_ext): def on_restore_bip39(self, seed, passphrase): f = lambda x: self.run('on_bip44', seed, passphrase, str(x)) - self.derivation_dialog(f, keystore.bip44_derivation_145(0)) + self.derivation_dialog(f, keystore.bip44_derivation_145(0), seed) def create_keystore(self, seed, passphrase): # auto-detect, prefers old, electrum, bip39 in that order. Since we diff --git a/electroncash_gui/qt/installwizard.py b/electroncash_gui/qt/installwizard.py index d04b0377fbdd..96a343abb7a6 100644 --- a/electroncash_gui/qt/installwizard.py +++ b/electroncash_gui/qt/installwizard.py @@ -498,6 +498,37 @@ def f(text): self.exec_layout(vbox, title, next_enabled=test(default)) return ' '.join(line.text().split()) + @wizard_dialog + def derivation_path_dialog(self, run_next, title, message, default, test, warning='', seed='', scannable=False): + def on_derivation_scan(derivation_line, seed): + derivation_scan_dialog = DerivationDialog(self, seed, DerivationPathScanner.DERIVATION_PATHS) + selected_path = derivation_scan_dialog.get_selected_path() + if selected_path: + derivation_line.setText(selected_path) + + vbox = QVBoxLayout() + vbox.addWidget(WWLabel(message)) + line = QLineEdit() + line.setText(default) + def f(text): + self.next_button.setEnabled(test(text)) + line.textEdited.connect(f) + vbox.addWidget(line) + vbox.addWidget(WWLabel(warning)) + + if scannable: + hbox = QHBoxLayout() + hbox.setContentsMargins(12,24,12,12) + but = QPushButton(_("Scan for derivation paths")) + hbox.addStretch(1) + hbox.addWidget(but) + vbox.addLayout(hbox) + but.clicked.connect(lambda: on_derivation_scan(line, seed)) + + self.exec_layout(vbox, title, next_enabled=test(default)) + return ' '.join(line.text().split()) + + @wizard_dialog def show_xpub_dialog(self, xpub, run_next): msg = ' '.join([ @@ -603,3 +634,113 @@ def do_dialog(): "After selecting a server, select a wallet file to open.")) QTimer.singleShot(10, do_dialog) return ret + +class DerivationPathScanner(QThread): + + DERIVATION_PATHS = [ + "m/44'/145'/0'", + "m/144'/44'/0'", + "m/144'/0'/0'", + "m/44'/0'/0'/0", + "m/0'/0", + "m/0", + "m/0'/0'", + "m/44'/145'/0'/0", + "m/44'/245'/0"] + + def __init__(self, parent, seed, update_table_cb): + QThread.__init__(self, parent) + self.update_table_cb = update_table_cb + self.parent = parent + self.seed = seed + self.aborting = False + + def run(self): + from electroncash.network import Network + network = Network.get_instance() + if not network: + for i, p in enumerate(self.DERIVATION_PATHS): + self.update_table_cb(i, _('Offline')) + return + + for i, p in enumerate(self.DERIVATION_PATHS): + if self.aborting: + return + from electroncash import keystore + from electroncash.wallet import Standard_Wallet + from electroncash.storage import WalletStorage + k = keystore.from_seed(self.seed, '', derivation=p, seed_type=self.parent.seed_type) + storage_path = self.parent.config.get_wallet_path() + "_not_saved_on_disk" + tmp_storage = WalletStorage(storage_path, in_memory_only=True) + tmp_storage.put('seed_type', self.parent.seed_type) + keys = k.dump() + tmp_storage.put('keystore', keys) + wallet = Standard_Wallet(tmp_storage) + wallet.start_threads(network) + wallet.synchronize() + wallet.wait_until_synchronized() + while network.is_connecting(): + time.sleep(0.1) + num_tx = len(wallet.get_history()) + wallet.clear_history() + self.update_table_cb(i, str(num_tx)) + +class DerivationDialog(QDialog): + scan_result_signal = pyqtSignal(object, object) + def __init__(self, parent, seed, paths): + QDialog.__init__(self, parent) + + self.setWindowTitle(_('Select derivation path')) + vbox = QVBoxLayout() + self.setLayout(vbox) + vbox.setContentsMargins(25, 25, 25, 25) + + self.table = QTableWidget(self) + self.table.setSelectionMode(QAbstractItemView.SingleSelection) + self.table.setSelectionBehavior(QAbstractItemView.SelectRows) + self.table.verticalHeader().setVisible(False) + self.table.verticalHeader().setSectionResizeMode(QHeaderView.ResizeToContents) + self.table.setSortingEnabled(False) + self.table.setColumnCount(2) + self.table.setRowCount(len(paths)) + self.table.setHorizontalHeaderItem(0, QTableWidgetItem(_('Path'))) + self.table.setHorizontalHeaderItem(1, QTableWidgetItem(_('Transactions'))) + self.table.horizontalHeader().setSectionResizeMode(0, QHeaderView.ResizeToContents) + self.table.horizontalHeader().setSectionResizeMode(1, QHeaderView.Stretch) + + for row, d_path in enumerate(paths): + path_item = QTableWidgetItem(d_path) + path_item.setFlags(Qt.ItemIsSelectable|Qt.ItemIsEnabled) + self.table.setItem(row, 0, path_item) + transaction_count_item = QTableWidgetItem(_('Scanning...')) + transaction_count_item.setFlags(Qt.ItemIsSelectable|Qt.ItemIsEnabled) + self.table.setItem(row, 1, transaction_count_item) + + self.table.cellDoubleClicked.connect(self.accept) + self.table.selectRow(0) + vbox.addWidget(self.table) + ok_but = OkButton(self) + buts = Buttons(CancelButton(self), ok_but) + vbox.addLayout(buts) + vbox.addStretch(1) + ok_but.setEnabled(True) + self.scan_result_signal.connect(self.update_table) + self.t = DerivationPathScanner(parent, seed, self.update_table_cb) + self.t.start() + + def update_table_cb(self, row, scan_result): + self.scan_result_signal.emit(row, scan_result) + + def update_table(self, row, scan_result): + self.table.item(row, 1).setText(scan_result) + + def get_selected_path(self): + if self.exec_(): + self.t.aborting = True + pathstr = self.table.selectionModel().selectedRows() + row = pathstr[0].row() + path_to_return = self.table.item(row, 0).text() + return path_to_return + + self.t.aborting = True + return None \ No newline at end of file From 430057742b7866bf2e0915a884d2854b689d8f2d Mon Sep 17 00:00:00 2001 From: Calin Culianu Date: Sun, 21 Mar 2021 03:31:42 +0200 Subject: [PATCH 016/208] Tweaks and fixups to the DerivationPathScanner - Made sure wallets are stopped with each iteration so they gc properly and so they don't leave threads alive - Made the operation cancelable without crashin - Auto select the most-found history row - Varous whitespace fixups - Misc. other fixes --- electroncash/base_wizard.py | 4 +- electroncash/wallet.py | 10 +- electroncash_gui/qt/installwizard.py | 172 +++++++++++++++++++-------- 3 files changed, 133 insertions(+), 53 deletions(-) diff --git a/electroncash/base_wizard.py b/electroncash/base_wizard.py index 03da7d015f74..b1656dacee2f 100644 --- a/electroncash/base_wizard.py +++ b/electroncash/base_wizard.py @@ -281,7 +281,9 @@ def derivation_dialog(self, f, default_derivation, seed=''): _("The placeholder value of {} is the default derivation for {} wallets.").format(default_derivation, self.wallet_type), ]) scannable = True if self.wallet_type == 'standard' else False - self.derivation_path_dialog(run_next=f, title=_('Derivation for {} wallet').format(self.wallet_type), message=message, default=default_derivation, test=bitcoin.is_bip32_derivation, seed=seed, scannable=scannable) + self.derivation_path_dialog(run_next=f, title=_('Derivation for {} wallet').format(self.wallet_type), + message=message, default=default_derivation, test=bitcoin.is_bip32_derivation, + seed=seed, scannable=scannable) def on_hw_derivation(self, name, device_info, derivation): from .keystore import hardware_keystore diff --git a/electroncash/wallet.py b/electroncash/wallet.py index c5887a01ec3e..0a7701f01423 100644 --- a/electroncash/wallet.py +++ b/electroncash/wallet.py @@ -44,7 +44,7 @@ from .i18n import ngettext from .util import (NotEnoughFunds, ExcessiveFee, PrintError, UserCancelled, profiler, format_satoshis, format_time, - finalization_print_error, to_string) + finalization_print_error, to_string, TimeoutException) from .address import Address, Script, ScriptOutput, PublicKey, OpCodes from .bitcoin import * @@ -2167,7 +2167,11 @@ def stop_pruned_txo_cleaner_thread(self): # a network call and it will eventually exit. t.join(timeout=3.0) - def wait_until_synchronized(self, callback=None): + def wait_until_synchronized(self, callback=None, *, timeout=None): + tstart = time.time() + def check_timed_out(): + if timeout is not None and time.time() - tstart > timeout: + raise TimeoutException() def wait_for_wallet(): self.set_up_to_date(False) while not self.is_up_to_date(): @@ -2178,12 +2182,14 @@ def wait_for_wallet(): len(self.addresses(True))) callback(msg) time.sleep(0.1) + check_timed_out() def wait_for_network(): while not self.network.is_connected(): if callback: msg = "%s \n" % (_("Connecting...")) callback(msg) time.sleep(0.1) + check_timed_out() # wait until we are connected, because the user # might have selected another server if self.network: diff --git a/electroncash_gui/qt/installwizard.py b/electroncash_gui/qt/installwizard.py index 96a343abb7a6..e661f6b486b3 100644 --- a/electroncash_gui/qt/installwizard.py +++ b/electroncash_gui/qt/installwizard.py @@ -1,6 +1,8 @@ # -*- mode: python3 -*- import os +import random import sys +import tempfile import threading import traceback import weakref @@ -9,10 +11,13 @@ from PyQt5.QtGui import * from PyQt5.QtWidgets import * -from electroncash import Wallet, WalletStorage -from electroncash.util import UserCancelled, InvalidPassword, finalization_print_error + +from electroncash import keystore, Wallet, WalletStorage +from electroncash.network import Network +from electroncash.util import UserCancelled, InvalidPassword, finalization_print_error, TimeoutException from electroncash.base_wizard import BaseWizard from electroncash.i18n import _ +from electroncash.wallet import Standard_Wallet from .seed_dialog import SeedLayout, KeysLayout from .network_dialog import NetworkChoiceLayout @@ -24,6 +29,7 @@ class GoBack(Exception): pass + MSG_GENERATING_WAIT = _("Electron Cash is generating your addresses, please wait...") MSG_ENTER_ANYTHING = _("Please enter a seed phrase, a master key, a list of " "Bitcoin addresses, or a list of private keys") @@ -73,7 +79,6 @@ def paintEvent(self, event): qp.end() - def wizard_dialog(func): def func_wrapper(*args, **kwargs): run_next = kwargs['run_next'] @@ -93,7 +98,6 @@ def func_wrapper(*args, **kwargs): return func_wrapper - # WindowModalDialog must come first as it overrides show_error class InstallWizard(QDialog, MessageBoxMixin, BaseWizard): @@ -284,8 +288,6 @@ def on_filename(filename): self.wallet = Wallet(self.storage) return self.wallet, password - - def finished(self): """Called in hardware client wrapper, in order to close popups.""" return @@ -414,7 +416,8 @@ def request_password(self, run_next): cannot go back, and instead the user can only cancel.""" return self.pw_layout(MSG_ENTER_PASSWORD, PW_NEW) - def _add_extra_button_to_layout(self, extra_button, layout): + @staticmethod + def _add_extra_button_to_layout(extra_button, layout): if (not isinstance(extra_button, (list, tuple)) or not len(extra_button) == 2): return @@ -427,7 +430,6 @@ def _add_extra_button_to_layout(self, extra_button, layout): layout.addLayout(hbox) but.clicked.connect(but_action) - @wizard_dialog def confirm_dialog(self, title, message, run_next, extra_button=None): self.confirm(message, title, extra_button=extra_button) @@ -475,7 +477,6 @@ def choice_dialog(self, title, message, choices, run_next, extra_button=None): action = c_values[clayout.selected_index()] return action - def query_choice(self, msg, choices): """called by hardware wallets""" clayout = ChoicesLayout(msg, choices) @@ -519,7 +520,7 @@ def f(text): if scannable: hbox = QHBoxLayout() hbox.setContentsMargins(12,24,12,12) - but = QPushButton(_("Scan for derivation paths")) + but = QPushButton(_("Scan Derivation Paths...")) hbox.addStretch(1) hbox.addWidget(but) vbox.addLayout(hbox) @@ -528,7 +529,6 @@ def f(text): self.exec_layout(vbox, title, next_enabled=test(default)) return ' '.join(line.text().split()) - @wizard_dialog def show_xpub_dialog(self, xpub, run_next): msg = ' '.join([ @@ -543,11 +543,11 @@ def show_xpub_dialog(self, xpub, run_next): def init_network(self, network): message = _("Electron Cash communicates with remote servers to get " - "information about your transactions and addresses. The " - "servers all fulfil the same purpose only differing in " - "hardware. In most cases you simply want to let Electron Cash " - "pick one at random. However if you prefer feel free to " - "select a server manually.") + "information about your transactions and addresses. The " + "servers all fulfil the same purpose only differing in " + "hardware. In most cases you simply want to let Electron Cash " + "pick one at random. However if you prefer feel free to " + "select a server manually.") choices = [_("Auto connect"), _("Select server manually")] title = _("How do you want to connect to a server? ") clayout = ChoicesLayout(message, choices) @@ -600,6 +600,7 @@ def on_n(n): return (m, n) linux_hw_wallet_support_dialog = None + def on_hw_wallet_support(self): ''' Overrides base wizard's noop impl. ''' if sys.platform.startswith("linux"): @@ -635,71 +636,114 @@ def do_dialog(): QTimer.singleShot(10, do_dialog) return ret + class DerivationPathScanner(QThread): DERIVATION_PATHS = [ "m/44'/145'/0'", + "m/44'/0'/0'", + "m/44'/245'/0'", "m/144'/44'/0'", "m/144'/0'/0'", "m/44'/0'/0'/0", - "m/0'/0", "m/0", + "m/0'", + "m/0'/0", "m/0'/0'", + "m/0'/0'/0'", "m/44'/145'/0'/0", - "m/44'/245'/0"] + "m/44'/245'/0", + "m/44'/245'/0'/0", + "m/49'/0'/0'", + "m/84'/0'/0'", + ] - def __init__(self, parent, seed, update_table_cb): + def __init__(self, parent, seed, seed_type, config, update_table_cb): QThread.__init__(self, parent) self.update_table_cb = update_table_cb - self.parent = parent self.seed = seed + self.seed_type = seed_type + self.config = config self.aborting = False + def notify_offline(self): + for i, p in enumerate(self.DERIVATION_PATHS): + self.update_table_cb(i, _('Offline')) + + def notify_timedout(self, i): + self.update_table_cb(i, _('Timed out')) + def run(self): - from electroncash.network import Network network = Network.get_instance() if not network: - for i, p in enumerate(self.DERIVATION_PATHS): - self.update_table_cb(i, _('Offline')) + self.notify_offline() return for i, p in enumerate(self.DERIVATION_PATHS): if self.aborting: return - from electroncash import keystore - from electroncash.wallet import Standard_Wallet - from electroncash.storage import WalletStorage - k = keystore.from_seed(self.seed, '', derivation=p, seed_type=self.parent.seed_type) - storage_path = self.parent.config.get_wallet_path() + "_not_saved_on_disk" + k = keystore.from_seed(self.seed, '', derivation=p, seed_type=self.seed_type) + p_safe = p.replace('/', '_').replace("'", 'h') + storage_path = os.path.join( + tempfile.gettempdir(), + p_safe + '_' + random.getrandbits(32).to_bytes(4, 'big').hex()[:8] + "_not_saved_" + ) tmp_storage = WalletStorage(storage_path, in_memory_only=True) - tmp_storage.put('seed_type', self.parent.seed_type) - keys = k.dump() - tmp_storage.put('keystore', keys) + tmp_storage.put('seed_type', self.seed_type) + tmp_storage.put('keystore', k.dump()) wallet = Standard_Wallet(tmp_storage) - wallet.start_threads(network) - wallet.synchronize() - wallet.wait_until_synchronized() - while network.is_connecting(): - time.sleep(0.1) - num_tx = len(wallet.get_history()) - wallet.clear_history() - self.update_table_cb(i, str(num_tx)) + try: + wallet.start_threads(network) + wallet.synchronize() + wallet.print_error("Scanning", p) + synched = False + for ctr in range(25): + try: + wallet.wait_until_synchronized(timeout=1.0) + synched = True + except TimeoutException: + wallet.print_error(f'timeout try {ctr+1}/25') + if self.aborting: + return + if not synched: + wallet.print_error("Timeout on", p) + self.notify_timedout(i) + continue + while network.is_connecting(): + time.sleep(0.1) + if self.aborting: + return + num_tx = len(wallet.get_history()) + self.update_table_cb(i, str(num_tx)) + finally: + wallet.clear_history() + wallet.stop_threads() + class DerivationDialog(QDialog): scan_result_signal = pyqtSignal(object, object) + def __init__(self, parent, seed, paths): QDialog.__init__(self, parent) - self.setWindowTitle(_('Select derivation path')) + self.seed = seed + self.seed_type = parent.seed_type + self.config = parent.config + self.max_seen = 0 + + self.setWindowTitle(_('Select Derivation Path')) vbox = QVBoxLayout() self.setLayout(vbox) - vbox.setContentsMargins(25, 25, 25, 25) + vbox.setContentsMargins(24, 24, 24, 24) + + self.label = QLabel(self) + vbox.addWidget(self.label) self.table = QTableWidget(self) self.table.setSelectionMode(QAbstractItemView.SingleSelection) self.table.setSelectionBehavior(QAbstractItemView.SelectRows) self.table.verticalHeader().setVisible(False) - self.table.verticalHeader().setSectionResizeMode(QHeaderView.ResizeToContents) + self.table.verticalHeader().setSectionResizeMode(QHeaderView.ResizeToContents) self.table.setSortingEnabled(False) self.table.setColumnCount(2) self.table.setRowCount(len(paths)) @@ -707,6 +751,7 @@ def __init__(self, parent, seed, paths): self.table.setHorizontalHeaderItem(1, QTableWidgetItem(_('Transactions'))) self.table.horizontalHeader().setSectionResizeMode(0, QHeaderView.ResizeToContents) self.table.horizontalHeader().setSectionResizeMode(1, QHeaderView.Stretch) + self.table.setMinimumHeight(350) for row, d_path in enumerate(paths): path_item = QTableWidgetItem(d_path) @@ -725,22 +770,49 @@ def __init__(self, parent, seed, paths): vbox.addStretch(1) ok_but.setEnabled(True) self.scan_result_signal.connect(self.update_table) - self.t = DerivationPathScanner(parent, seed, self.update_table_cb) - self.t.start() + self.t = None + + def set_scan_progress(self, n): + self.label.setText(_('Scanned {}/{}').format(n, len(DerivationPathScanner.DERIVATION_PATHS))) + + def kill_t(self): + if self.t and self.t.isRunning(): + self.t.aborting = True + self.t.wait(5000) + + def showEvent(self, e): + super().showEvent(e) + if e.isAccepted(): + self.kill_t() + self.t = DerivationPathScanner(self, self.seed, self.seed_type, self.config, self.update_table_cb) + self.max_seen = 0 + self.set_scan_progress(0) + self.t.start() + + def closeEvent(self, e): + super().closeEvent(e) + if e.isAccepted(): + self.kill_t() def update_table_cb(self, row, scan_result): self.scan_result_signal.emit(row, scan_result) def update_table(self, row, scan_result): + self.set_scan_progress(row+1) + try: + num = int(scan_result) + if num > self.max_seen: + self.table.selectRow(row) + self.max_seen = num + except (ValueError, TypeError): + pass self.table.item(row, 1).setText(scan_result) def get_selected_path(self): + path_to_return = None if self.exec_(): - self.t.aborting = True pathstr = self.table.selectionModel().selectedRows() row = pathstr[0].row() - path_to_return = self.table.item(row, 0).text() - return path_to_return - - self.t.aborting = True - return None \ No newline at end of file + path_to_return = self.table.item(row, 0).text() + self.kill_t() + return path_to_return From b0d64f17d697e56bc9f536a3f6c6a7f4419cb7a8 Mon Sep 17 00:00:00 2001 From: Calin Culianu Date: Sun, 21 Mar 2021 03:52:05 +0200 Subject: [PATCH 017/208] Derivation scanner: use deleteLater() This is just to kill the widget immediately when we are done with it. --- electroncash_gui/qt/installwizard.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/electroncash_gui/qt/installwizard.py b/electroncash_gui/qt/installwizard.py index e661f6b486b3..28d3319d4e37 100644 --- a/electroncash_gui/qt/installwizard.py +++ b/electroncash_gui/qt/installwizard.py @@ -503,9 +503,11 @@ def f(text): def derivation_path_dialog(self, run_next, title, message, default, test, warning='', seed='', scannable=False): def on_derivation_scan(derivation_line, seed): derivation_scan_dialog = DerivationDialog(self, seed, DerivationPathScanner.DERIVATION_PATHS) + destroyed_print_error(derivation_scan_dialog) selected_path = derivation_scan_dialog.get_selected_path() if selected_path: derivation_line.setText(selected_path) + derivation_scan_dialog.deleteLater() vbox = QVBoxLayout() vbox.addWidget(WWLabel(message)) From 51c8cc6a8aa0ecbade4ff06a81ccea42ae457364 Mon Sep 17 00:00:00 2001 From: Axel Gembe Date: Sun, 21 Mar 2021 10:43:25 +0700 Subject: [PATCH 018/208] Build: Add cffi to the requirements When using dnspython for dnssec cffi is required. closes #2201 --- contrib/deterministic-build/requirements.txt | 38 ++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/contrib/deterministic-build/requirements.txt b/contrib/deterministic-build/requirements.txt index 6ec0e579bab2..728791246403 100644 --- a/contrib/deterministic-build/requirements.txt +++ b/contrib/deterministic-build/requirements.txt @@ -1,3 +1,41 @@ +cffi==1.14.5 \ + --hash=sha256:005a36f41773e148deac64b08f233873a4d0c18b053d37da83f6af4d9087b813 \ + --hash=sha256:0857f0ae312d855239a55c81ef453ee8fd24136eaba8e87a2eceba644c0d4c06 \ + --hash=sha256:1071534bbbf8cbb31b498d5d9db0f274f2f7a865adca4ae429e147ba40f73dea \ + --hash=sha256:158d0d15119b4b7ff6b926536763dc0714313aa59e320ddf787502c70c4d4bee \ + --hash=sha256:1f436816fc868b098b0d63b8920de7d208c90a67212546d02f84fe78a9c26396 \ + --hash=sha256:2894f2df484ff56d717bead0a5c2abb6b9d2bf26d6960c4604d5c48bbc30ee73 \ + --hash=sha256:29314480e958fd8aab22e4a58b355b629c59bf5f2ac2492b61e3dc06d8c7a315 \ + --hash=sha256:34eff4b97f3d982fb93e2831e6750127d1355a923ebaeeb565407b3d2f8d41a1 \ + --hash=sha256:35f27e6eb43380fa080dccf676dece30bef72e4a67617ffda586641cd4508d49 \ + --hash=sha256:3d3dd4c9e559eb172ecf00a2a7517e97d1e96de2a5e610bd9b68cea3925b4892 \ + --hash=sha256:43e0b9d9e2c9e5d152946b9c5fe062c151614b262fda2e7b201204de0b99e482 \ + --hash=sha256:48e1c69bbacfc3d932221851b39d49e81567a4d4aac3b21258d9c24578280058 \ + --hash=sha256:51182f8927c5af975fece87b1b369f722c570fe169f9880764b1ee3bca8347b5 \ + --hash=sha256:58e3f59d583d413809d60779492342801d6e82fefb89c86a38e040c16883be53 \ + --hash=sha256:5de7970188bb46b7bf9858eb6890aad302577a5f6f75091fd7cdd3ef13ef3045 \ + --hash=sha256:65fa59693c62cf06e45ddbb822165394a288edce9e276647f0046e1ec26920f3 \ + --hash=sha256:69e395c24fc60aad6bb4fa7e583698ea6cc684648e1ffb7fe85e3c1ca131a7d5 \ + --hash=sha256:6c97d7350133666fbb5cf4abdc1178c812cb205dc6f41d174a7b0f18fb93337e \ + --hash=sha256:6e4714cc64f474e4d6e37cfff31a814b509a35cb17de4fb1999907575684479c \ + --hash=sha256:72d8d3ef52c208ee1c7b2e341f7d71c6fd3157138abf1a95166e6165dd5d4369 \ + --hash=sha256:8ae6299f6c68de06f136f1f9e69458eae58f1dacf10af5c17353eae03aa0d827 \ + --hash=sha256:8b198cec6c72df5289c05b05b8b0969819783f9418e0409865dac47288d2a053 \ + --hash=sha256:99cd03ae7988a93dd00bcd9d0b75e1f6c426063d6f03d2f90b89e29b25b82dfa \ + --hash=sha256:9cf8022fb8d07a97c178b02327b284521c7708d7c71a9c9c355c178ac4bbd3d4 \ + --hash=sha256:9de2e279153a443c656f2defd67769e6d1e4163952b3c622dcea5b08a6405322 \ + --hash=sha256:9e93e79c2551ff263400e1e4be085a1210e12073a31c2011dbbda14bda0c6132 \ + --hash=sha256:9ff227395193126d82e60319a673a037d5de84633f11279e336f9c0f189ecc62 \ + --hash=sha256:a465da611f6fa124963b91bf432d960a555563efe4ed1cc403ba5077b15370aa \ + --hash=sha256:ad17025d226ee5beec591b52800c11680fca3df50b8b29fe51d882576e039ee0 \ + --hash=sha256:afb29c1ba2e5a3736f1c301d9d0abe3ec8b86957d04ddfa9d7a6a42b9367e396 \ + --hash=sha256:b85eb46a81787c50650f2392b9b4ef23e1f126313b9e0e9013b35c15e4288e2e \ + --hash=sha256:bb89f306e5da99f4d922728ddcd6f7fcebb3241fc40edebcb7284d7514741991 \ + --hash=sha256:cbde590d4faaa07c72bf979734738f328d239913ba3e043b1e98fe9a39f8b2b6 \ + --hash=sha256:cd2868886d547469123fadc46eac7ea5253ea7fcb139f12e1dfc2bbd406427d1 \ + --hash=sha256:d42b11d692e11b6634f7613ad8df5d6d5f8875f5d48939520d351007b3c13406 \ + --hash=sha256:f2d45f97ab6bb54753eab54fffe75aaf3de4ff2341c9daee1987ee1837636f1d \ + --hash=sha256:fd78e5fee591709f32ef6edb9a015b4aa1a5022598e36227500c8f4e02328d9c certifi==2020.12.5 \ --hash=sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c \ --hash=sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830 From 7e25aaabe431f302e469901684fd0e5cc9334849 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aldin=20Kova=C4=8Devi=C4=87?= Date: Mon, 22 Mar 2021 23:02:21 +0100 Subject: [PATCH 019/208] Android: add new layout screens for the choice between standard and multi-sig wallet. --- .../src/main/res/layout/choose_keystore.xml | 39 +++++++++++++++ .../app/src/main/res/layout/wallet_new.xml | 48 +++++++++---------- android/app/src/main/res/menu/wallet_kind.xml | 12 +++++ 3 files changed, 75 insertions(+), 24 deletions(-) create mode 100644 android/app/src/main/res/layout/choose_keystore.xml create mode 100644 android/app/src/main/res/menu/wallet_kind.xml diff --git a/android/app/src/main/res/layout/choose_keystore.xml b/android/app/src/main/res/layout/choose_keystore.xml new file mode 100644 index 000000000000..32707bc19539 --- /dev/null +++ b/android/app/src/main/res/layout/choose_keystore.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/android/app/src/main/res/layout/wallet_new.xml b/android/app/src/main/res/layout/wallet_new.xml index c32854d7da59..38ac05c7710d 100644 --- a/android/app/src/main/res/layout/wallet_new.xml +++ b/android/app/src/main/res/layout/wallet_new.xml @@ -9,35 +9,14 @@ android:layout_width="match_parent" android:layout_height="wrap_content"> - - - - + app:layout_constraintStart_toStartOf="@+id/spnWalletKind" + app:layout_constraintTop_toBottomOf="@+id/spnWalletKind" /> + app:constraint_referenced_ids="textView6,textView7" + tools:layout_editor_absoluteY="266dp" /> + + + + \ No newline at end of file diff --git a/android/app/src/main/res/menu/wallet_kind.xml b/android/app/src/main/res/menu/wallet_kind.xml new file mode 100644 index 000000000000..8913e3030b0b --- /dev/null +++ b/android/app/src/main/res/menu/wallet_kind.xml @@ -0,0 +1,12 @@ + + + + + + + + \ No newline at end of file From 67f9255e69481cd623b80a22952c47882a9f64af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aldin=20Kova=C4=8Devi=C4=87?= Date: Mon, 22 Mar 2021 23:02:58 +0100 Subject: [PATCH 020/208] Android: reorganize code for creating a standard wallet. --- .../electroncash/electroncash3/NewWallet.kt | 85 ++++++++++++++----- 1 file changed, 66 insertions(+), 19 deletions(-) diff --git a/android/app/src/main/java/org/electroncash/electroncash3/NewWallet.kt b/android/app/src/main/java/org/electroncash/electroncash3/NewWallet.kt index 92ab12fe2abc..bd5dc0c53803 100644 --- a/android/app/src/main/java/org/electroncash/electroncash3/NewWallet.kt +++ b/android/app/src/main/java/org/electroncash/electroncash3/NewWallet.kt @@ -11,6 +11,7 @@ import androidx.fragment.app.DialogFragment import com.chaquo.python.Kwarg import com.chaquo.python.PyException import com.google.zxing.integration.android.IntentIntegrator +import kotlinx.android.synthetic.main.choose_keystore.* import kotlinx.android.synthetic.main.wallet_new.* import kotlinx.android.synthetic.main.wallet_new_2.* import kotlin.properties.Delegates.notNull @@ -30,7 +31,8 @@ class NewWalletDialog1 : AlertDialogFragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - spnType.adapter = MenuAdapter(context!!, R.menu.wallet_type) + // spnType.adapter = MenuAdapter(context!!, R.menu.wallet_type) + spnWalletKind.adapter = MenuAdapter(context!!, R.menu.wallet_kind) } override fun onShowDialog() { @@ -45,21 +47,19 @@ class NewWalletDialog1 : AlertDialogFragment() { putString("password", password) } - val walletType = spnType.selectedItemId.toInt() - if (walletType in listOf(R.id.menuCreateSeed, R.id.menuRestoreSeed)) { - nextDialog = NewWalletSeedDialog() - val seed = if (walletType == R.id.menuCreateSeed) - daemonModel.commands.callAttr("make_seed").toString() - else null - arguments.putString("seed", seed) - } else if (walletType == R.id.menuImport) { - nextDialog = NewWalletImportDialog() - } else if (walletType == R.id.menuImportMaster) { - nextDialog = NewWalletImportMasterDialog() - } else { - throw Exception("Unknown item: ${spnType.selectedItem}") + when (spnWalletKind.selectedItemId.toInt()) { + R.id.menuStandardWallet -> { + nextDialog = KeystoreDialog() + showDialog(this, nextDialog.apply { setArguments(arguments) }) + } + R.id.menuMultisigWallet -> { + // TODO + } + else -> { + throw Exception("Unknown item: ${spnWalletKind.selectedItem}") + } } - showDialog(this, nextDialog.apply { setArguments(arguments) }) + } catch (e: ToastException) { e.show() } } } @@ -94,15 +94,61 @@ fun confirmPassword(dialog: Dialog): String { return password } +// Choose the way of generating the wallet (new seed, import seed, etc.) +class KeystoreDialog : TaskLauncherDialog() { + var input: String by notNull() + + override fun onBuildDialog(builder: AlertDialog.Builder) { + builder.setTitle(R.string.New_wallet) + .setView(R.layout.choose_keystore) + .setPositiveButton(android.R.string.ok, null) + .setNegativeButton(R.string.back, null) + } + + override fun doInBackground(): String { + TODO("Not yet implemented") + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + spnType.adapter = MenuAdapter(context!!, R.menu.wallet_type) + } + + override fun onShowDialog() { + dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener { + try { + + val nextDialog: DialogFragment + val keystoreType = spnType.selectedItemId.toInt() + + if (keystoreType in listOf(R.id.menuCreateSeed, R.id.menuRestoreSeed)) { + nextDialog = NewWalletSeedDialog() + val seed = if (keystoreType == R.id.menuCreateSeed) + daemonModel.commands.callAttr("make_seed").toString() + else null + arguments!!.putString("seed", seed) + } else if (keystoreType == R.id.menuImport) { + nextDialog = NewWalletImportDialog() + } else if (keystoreType == R.id.menuImportMaster) { + nextDialog = NewWalletImportMasterDialog() + } else { + throw Exception("Unknown item: ${spnType.selectedItem}") + } + nextDialog.setArguments(arguments) + showDialog(this, nextDialog) + } catch (e: ToastException) { e.show() } + } + } +} abstract class NewWalletDialog2 : TaskLauncherDialog() { var input: String by notNull() override fun onBuildDialog(builder: AlertDialog.Builder) { builder.setTitle(R.string.New_wallet) - .setView(R.layout.wallet_new_2) - .setPositiveButton(android.R.string.ok, null) - .setNegativeButton(R.string.back, null) + .setView(R.layout.wallet_new_2) + .setPositiveButton(android.R.string.ok, null) + .setNegativeButton(R.string.back, null) } override fun onPreExecute() { @@ -120,7 +166,8 @@ abstract class NewWalletDialog2 : TaskLauncherDialog() { abstract fun onCreateWallet(name: String, password: String) override fun onPostExecute(result: String) { - (targetFragment as NewWalletDialog1).dismiss() + (targetFragment as KeystoreDialog).dismiss() + (targetFragment!!.targetFragment as NewWalletDialog1).dismiss() daemonModel.commands.callAttr("select_wallet", result) (activity as MainActivity).updateDrawer() } From 4c4b5663ab66828c2d9c5296c30042342a83eb44 Mon Sep 17 00:00:00 2001 From: Axel Gembe Date: Tue, 23 Mar 2021 12:04:37 +0700 Subject: [PATCH 021/208] Tor: Update to version 0.4.5.7 with some patches This has an important fix: * Major bugfixes (security, denial of service): Disable the dump_desc() function that we used to dump unparseable information to disk. It was called incorrectly in several places, in a way that could lead to excessive CPU usage. Fixes bug 40286; bugfix on 0.2.2.1-alpha. This bug is also tracked as TROVE-2021- 001 and CVE-2021-28089. Tree built from: https://github.com/EchterAgo/tor/commits/electroncash_0_4_5_7 This includes a static linking fix as well as Autoconf 2.70 compatibility. --- contrib/tor | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/tor b/contrib/tor index a72b0b43c8ee..7ce4ae3443b0 160000 --- a/contrib/tor +++ b/contrib/tor @@ -1 +1 @@ -Subproject commit a72b0b43c8eeb0036130bf0232d116fb38c28b26 +Subproject commit 7ce4ae3443b0a9319ebacc9f1a28898bf0dc02d2 From c44d2aa4fb413785236d9a5c20ee028540ef0490 Mon Sep 17 00:00:00 2001 From: Axel Gembe Date: Tue, 23 Mar 2021 13:01:03 +0700 Subject: [PATCH 022/208] OpenSSL: Update to version 1.1.1j This has an important fix: * Fixed the X509_issuer_and_serial_hash() function. It attempts to create a unique hash value based on the issuer and serial number data contained within an X509 certificate. However it was failing to correctly handle any errors that may occur while parsing the issuer field (which might occur if the issuer field is maliciously constructed). This may subsequently result in a NULL pointer deref and a crash leading to a potential denial of service attack. ([CVE-2021-23841]) There are more security fixes, but they do not affect us. --- contrib/openssl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/openssl b/contrib/openssl index 90cebd1b216e..52c587d60be6 160000 --- a/contrib/openssl +++ b/contrib/openssl @@ -1 +1 @@ -Subproject commit 90cebd1b216e0a160fcfd8e0eddca47dad47c183 +Subproject commit 52c587d60be67c337364b830dd3fdc15404a2f04 From cc1c9d7ddf040df1858094a994d02b3f2a0cbdc8 Mon Sep 17 00:00:00 2001 From: Jonas Lundqvist Date: Tue, 23 Mar 2021 10:01:04 +0100 Subject: [PATCH 023/208] Skip path derivation scan if seed is unavailable HW wallets have no seed available but is still considered 'standard'. Closes #2207 --- electroncash/base_wizard.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/electroncash/base_wizard.py b/electroncash/base_wizard.py index b1656dacee2f..0863cfbb3aaf 100644 --- a/electroncash/base_wizard.py +++ b/electroncash/base_wizard.py @@ -280,7 +280,7 @@ def derivation_dialog(self, f, default_derivation, seed=''): _("If you want the wallet to use Bitcoin Cash addresses use m/44'/145'/0'"), _("The placeholder value of {} is the default derivation for {} wallets.").format(default_derivation, self.wallet_type), ]) - scannable = True if self.wallet_type == 'standard' else False + scannable = True if self.wallet_type == 'standard' and seed else False self.derivation_path_dialog(run_next=f, title=_('Derivation for {} wallet').format(self.wallet_type), message=message, default=default_derivation, test=bitcoin.is_bip32_derivation, seed=seed, scannable=scannable) From 7405d84641e6e0deab70541513f2f3b7138ab47c Mon Sep 17 00:00:00 2001 From: Daniel Gonzalez Gasull Date: Wed, 24 Mar 2021 01:48:21 +0000 Subject: [PATCH 024/208] Shorten boolean expression, use double quotes --- electroncash/base_wizard.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/electroncash/base_wizard.py b/electroncash/base_wizard.py index 0863cfbb3aaf..8ecc05da7890 100644 --- a/electroncash/base_wizard.py +++ b/electroncash/base_wizard.py @@ -280,7 +280,7 @@ def derivation_dialog(self, f, default_derivation, seed=''): _("If you want the wallet to use Bitcoin Cash addresses use m/44'/145'/0'"), _("The placeholder value of {} is the default derivation for {} wallets.").format(default_derivation, self.wallet_type), ]) - scannable = True if self.wallet_type == 'standard' and seed else False + scannable = (self.wallet_type == "standard") and bool(seed) self.derivation_path_dialog(run_next=f, title=_('Derivation for {} wallet').format(self.wallet_type), message=message, default=default_derivation, test=bitcoin.is_bip32_derivation, seed=seed, scannable=scannable) From 05894d2f491de7aed1acd86546fcb38774789ef0 Mon Sep 17 00:00:00 2001 From: Daniel Gonzalez Gasull Date: Wed, 24 Mar 2021 06:11:21 +0000 Subject: [PATCH 025/208] Remove unnecessary parentheses --- electroncash/base_wizard.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/electroncash/base_wizard.py b/electroncash/base_wizard.py index 8ecc05da7890..95cb1de17491 100644 --- a/electroncash/base_wizard.py +++ b/electroncash/base_wizard.py @@ -280,7 +280,7 @@ def derivation_dialog(self, f, default_derivation, seed=''): _("If you want the wallet to use Bitcoin Cash addresses use m/44'/145'/0'"), _("The placeholder value of {} is the default derivation for {} wallets.").format(default_derivation, self.wallet_type), ]) - scannable = (self.wallet_type == "standard") and bool(seed) + scannable = self.wallet_type == "standard" and bool(seed) self.derivation_path_dialog(run_next=f, title=_('Derivation for {} wallet').format(self.wallet_type), message=message, default=default_derivation, test=bitcoin.is_bip32_derivation, seed=seed, scannable=scannable) From bc20c4169639c85b1dba2055ec6d9d749363c4d3 Mon Sep 17 00:00:00 2001 From: Daniel Gonzalez Gasull Date: Wed, 24 Mar 2021 07:46:43 +0000 Subject: [PATCH 026/208] Style changes (from backport), small refactorings Backport from Electrum for small style changes. Also refactored two long lines and changed two lists to sets. --- contrib/sign_packages | 5 +-- electroncash/coinchooser.py | 2 +- electroncash/mnemonic.py | 2 +- electroncash/old_mnemonic.py | 6 ++-- electroncash/plot.py | 2 +- electroncash/tests/test_bitcoin.py | 2 +- electroncash_gui/qt/main_window.py | 12 +++---- electroncash_gui/qt/network_dialog.py | 12 +++---- electroncash_gui/qt/password_dialog.py | 2 +- electroncash_gui/qt/qrcodewidget.py | 2 +- electroncash_gui/qt/util.py | 4 +-- electroncash_gui/stdio.py | 12 +++---- electroncash_gui/text.py | 38 +++++++++++------------ electroncash_plugins/hw_wallet/cmdline.py | 2 +- electroncash_plugins/keepkey/keepkey.py | 8 ++--- electroncash_plugins/ledger/auth2fa.py | 4 +-- electroncash_plugins/ledger/ledger.py | 2 +- 17 files changed, 57 insertions(+), 60 deletions(-) diff --git a/contrib/sign_packages b/contrib/sign_packages index d11ef5fc31f2..b7c3a37c59b4 100755 --- a/contrib/sign_packages +++ b/contrib/sign_packages @@ -10,9 +10,6 @@ if __name__ == '__main__': for f in os.listdir('.'): if f.endswith('asc'): continue - os.system( "gpg --sign --armor --detach --passphrase \"%s\" %s"%(password, f) ) + os.system("gpg --sign --armor --detach --passphrase \"%s\" %s"%(password, f)) os.chdir("..") - - - diff --git a/electroncash/coinchooser.py b/electroncash/coinchooser.py index 798c11a7858c..51a05c1ccf3c 100644 --- a/electroncash/coinchooser.py +++ b/electroncash/coinchooser.py @@ -221,7 +221,7 @@ def bucket_candidates(self, buckets, sufficient_funds): # Add all singletons for n, bucket in enumerate(buckets): if sufficient_funds([bucket]): - candidates.add((n, )) + candidates.add((n,)) # And now some random ones attempts = min(100, (len(buckets) - 1) * 10 + 1) diff --git a/electroncash/mnemonic.py b/electroncash/mnemonic.py index 077160b2b641..0800c2a93177 100644 --- a/electroncash/mnemonic.py +++ b/electroncash/mnemonic.py @@ -52,7 +52,7 @@ (0x2B740, 0x2B81F, 'CJK Unified Ideographs Extension D'), (0xF900, 0xFAFF, 'CJK Compatibility Ideographs'), (0x2F800, 0x2FA1D, 'CJK Compatibility Ideographs Supplement'), - (0x3190, 0x319F , 'Kanbun'), + (0x3190, 0x319F, 'Kanbun'), (0x2E80, 0x2EFF, 'CJK Radicals Supplement'), (0x2F00, 0x2FDF, 'CJK Radicals'), (0x31C0, 0x31EF, 'CJK Strokes'), diff --git a/electroncash/old_mnemonic.py b/electroncash/old_mnemonic.py index e2102b45223b..ef793dd1ddff 100644 --- a/electroncash/old_mnemonic.py +++ b/electroncash/old_mnemonic.py @@ -1661,7 +1661,7 @@ # Note about US patent no 5892470: Here each word does not represent a given digit. # Instead, the digit represented by a word is variable, it depends on the previous word. -def mn_encode( message ): +def mn_encode(message): assert len(message) % 8 == 0 out = [] for i in range(len(message)//8): @@ -1670,11 +1670,11 @@ def mn_encode( message ): w1 = (x%n) w2 = ((x//n) + w1)%n w3 = ((x//n//n) + w2)%n - out += [ words[w1], words[w2], words[w3] ] + out += [words[w1], words[w2], words[w3]] return out -def mn_decode( wlist ): +def mn_decode(wlist): out = '' for i in range(len(wlist)//3): word1, word2, word3 = wlist[3*i:3*i+3] diff --git a/electroncash/plot.py b/electroncash/plot.py index d0ffc5f0d4e3..59304c53a47e 100644 --- a/electroncash/plot.py +++ b/electroncash/plot.py @@ -33,7 +33,7 @@ def plot_history(wallet, history): f, axarr = plt.subplots(2, sharex=True) plt.subplots_adjust(bottom=0.2) - plt.xticks( rotation=25 ) + plt.xticks(rotation=25) ax = plt.gca() plt.ylabel('BCH') plt.xlabel('Month') diff --git a/electroncash/tests/test_bitcoin.py b/electroncash/tests/test_bitcoin.py index 1e8cb3320c7c..cc96dfb5f60a 100644 --- a/electroncash/tests/test_bitcoin.py +++ b/electroncash/tests/test_bitcoin.py @@ -154,7 +154,7 @@ def test_hash(self): def test_var_int(self): for i in range(0xfd): - self.assertEqual(var_int(i), "{:02x}".format(i) ) + self.assertEqual(var_int(i), "{:02x}".format(i)) self.assertEqual(var_int(0xfd), "fdfd00") self.assertEqual(var_int(0xfe), "fdfe00") diff --git a/electroncash_gui/qt/main_window.py b/electroncash_gui/qt/main_window.py index 19409222a3ca..24b3b2d9497d 100644 --- a/electroncash_gui/qt/main_window.py +++ b/electroncash_gui/qt/main_window.py @@ -968,7 +968,6 @@ def update_status(self): _("Balance: {amount_and_unit}").format( amount_and_unit=self.format_amount_and_units(c)) ] - if u: text_items.append(_("[{amount} unconfirmed]").format( amount=self.format_amount(u, True).strip())) @@ -1006,11 +1005,10 @@ def update_status(self): self.tray.setToolTip("%s (%s)" % (text, self.wallet.basename())) self.balance_label.setText(text) - self.status_button.setIcon( icon ) + self.status_button.setIcon(icon) self.status_button.setStatusTip( status_tip ) run_hook('window_update_status', self) - def update_wallet(self): self.need_update.set() # will enqueue an _update_wallet() call in at most 0.5 seconds from now. @@ -2876,7 +2874,7 @@ def create_status_bar(self): self.update_available_button.setVisible(bool(self.gui_object.new_version_available)) # if hidden now gets unhidden by on_update_available when a new version comes in self.lock_icon = QIcon() - self.password_button = StatusBarButton(self.lock_icon, _("Password"), self.change_password_dialog ) + self.password_button = StatusBarButton(self.lock_icon, _("Password"), self.change_password_dialog) sb.addPermanentWidget(self.password_button) self.addr_converter_button = StatusBarButton( @@ -2889,8 +2887,10 @@ def create_status_bar(self): self.addr_converter_button.setHidden(self.gui_object.is_cashaddr_status_button_hidden()) self.gui_object.cashaddr_status_button_hidden_signal.connect(self.addr_converter_button.setHidden) - sb.addPermanentWidget(StatusBarButton(QIcon(":icons/preferences.svg"), _("Preferences"), self.settings_dialog ) ) - self.seed_button = StatusBarButton(QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog ) + q_icon_prefs = QIcon(":icons/preferences.svg"), _("Preferences"), self.settings_dialog) + sb.addPermanentWidget(StatusBarButton(q_icon_prefs)) + q_icon_seed = QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog) + self.seed_button = StatusBarButton(q_icon_seed) sb.addPermanentWidget(self.seed_button) weakSelf = Weak.ref(self) gui_object = self.gui_object diff --git a/electroncash_gui/qt/network_dialog.py b/electroncash_gui/qt/network_dialog.py index 342cde824950..9d7b127aa225 100644 --- a/electroncash_gui/qt/network_dialog.py +++ b/electroncash_gui/qt/network_dialog.py @@ -159,7 +159,7 @@ def create_menu(self, position): menu.exec_(self.viewport().mapToGlobal(position)) def keyPressEvent(self, event): - if event.key() in [ Qt.Key_F2, Qt.Key_Return ]: + if event.key() in {Qt.Key_F2, Qt.Key_Return}: item, col = self.currentItem(), self.currentColumn() if item and col > -1: self.on_activated(item, col) @@ -927,11 +927,11 @@ def set_server(self, onion_hack=False): def set_proxy(self): host, port, protocol, proxy, auto_connect = self.network.get_parameters() if self.proxy_cb.isChecked(): - proxy = { 'mode':str(self.proxy_mode.currentText()).lower(), - 'host':str(self.proxy_host.text()), - 'port':str(self.proxy_port.text()), - 'user':str(self.proxy_user.text()), - 'password':str(self.proxy_password.text())} + proxy = {'mode':str(self.proxy_mode.currentText()).lower(), + 'host':str(self.proxy_host.text()), + 'port':str(self.proxy_port.text()), + 'user':str(self.proxy_user.text()), + 'password':str(self.proxy_password.text())} else: proxy = None self.network.set_parameters(host, port, protocol, proxy, auto_connect) diff --git a/electroncash_gui/qt/password_dialog.py b/electroncash_gui/qt/password_dialog.py index d47c42297775..fa07786c90e1 100644 --- a/electroncash_gui/qt/password_dialog.py +++ b/electroncash_gui/qt/password_dialog.py @@ -48,7 +48,7 @@ def check_password_strength(password): num = re.search("[0-9]", password) is not None and re.match("^[0-9]*$", password) is None caps = password != password.upper() and password != password.lower() extra = re.match("^[a-zA-Z0-9]*$", password) is None - score = len(password)*( n + caps + num + extra)/20 + score = len(password)*(n + caps + num + extra)/20 password_strength = {0:"Weak",1:"Medium",2:"Strong",3:"Very Strong"} return password_strength[min(3, int(score))] diff --git a/electroncash_gui/qt/qrcodewidget.py b/electroncash_gui/qt/qrcodewidget.py index 7c5968430084..fb60555660b9 100644 --- a/electroncash_gui/qt/qrcodewidget.py +++ b/electroncash_gui/qt/qrcodewidget.py @@ -80,7 +80,7 @@ def paintEvent(self, e): margin = 5 framesize = min(r.width(), r.height()) - boxsize = int( (framesize - 2*margin)/k ) + boxsize = int((framesize - 2*margin)/k) size = k*boxsize left = (r.width() - size)/2 top = (r.height() - size)/2 diff --git a/electroncash_gui/qt/util.py b/electroncash_gui/qt/util.py index 1e9da39ffac1..4e734de49d61 100644 --- a/electroncash_gui/qt/util.py +++ b/electroncash_gui/qt/util.py @@ -531,7 +531,7 @@ def filename_field(config, defaultname, select_msg): hbox = QHBoxLayout() directory = config.get('io_dir', os.path.expanduser('~')) - path = os.path.join( directory, defaultname ) + path = os.path.join(directory, defaultname) filename_e = QLineEdit() filename_e.setText(path) @@ -660,7 +660,7 @@ def editItem(self, item, column): item.setFlags(item.flags() & ~Qt.ItemIsEditable) def keyPressEvent(self, event): - if event.key() in [ Qt.Key_F2, Qt.Key_Return ] and self.editor is None: + if event.key() in {Qt.Key_F2, Qt.Key_Return} and self.editor is None: item, col = self.currentItem(), self.currentColumn() if item and col > -1: self.on_activated(item, col) diff --git a/electroncash_gui/stdio.py b/electroncash_gui/stdio.py index 3e4bcaa47c2b..90b7bb48fd4c 100644 --- a/electroncash_gui/stdio.py +++ b/electroncash_gui/stdio.py @@ -46,7 +46,7 @@ def __init__(self, config, daemon, plugins): _("[r] - show own receipt addresses"), \ _("[c] - display contacts"), \ _("[b] - print server banner"), \ - _("[q] - quit") ] + _("[q] - quit")] self.num_commands = len(self.commands) def on_network(self, event, *args): @@ -101,7 +101,7 @@ def print_history(self): label = self.wallet.get_label(tx_hash) messages.append( format_str%( time_str, label, format_satoshis(delta, whitespaces=True), format_satoshis(balance, whitespaces=True) ) ) - self.print_list(messages[::-1], format_str%( _("Date"), _("Description"), _("Amount"), _("Balance"))) + self.print_list(messages[::-1], format_str%(_("Date"), _("Description"), _("Amount"), _("Balance"))) def print_balance(self): @@ -110,7 +110,7 @@ def print_balance(self): def get_balance(self): if self.wallet.network.is_connected(): if not self.wallet.up_to_date: - msg = _( "Synchronizing..." ) + msg = _("Synchronizing...") else: c, u, x = self.wallet.get_balance() msg = _("Balance")+": %f "%(PyDecimal(c) / COIN) @@ -119,7 +119,7 @@ def get_balance(self): if x: msg += " [%f unmatured]"%(PyDecimal(x) / COIN) else: - msg = _( "Not connected" ) + msg = _("Not connected") return(msg) @@ -148,8 +148,8 @@ def send_order(self): self.do_send() def print_banner(self): - for i, x in enumerate( self.wallet.network.banner.split('\n') ): - print( x ) + for i, x in enumerate(self.wallet.network.banner.split('\n')): + print(x) def print_list(self, lst, firstline): lst = list(lst) diff --git a/electroncash_gui/text.py b/electroncash_gui/text.py index 58c817b67d04..57b354f71980 100644 --- a/electroncash_gui/text.py +++ b/electroncash_gui/text.py @@ -79,7 +79,7 @@ def verify_seed(self): def get_string(self, y, x): self.set_cursor(1) curses.echo() - self.stdscr.addstr( y, x, " "*20, curses.A_REVERSE) + self.stdscr.addstr(y, x, " "*20, curses.A_REVERSE) s = self.stdscr.getstr(y,x) curses.noecho() self.set_cursor(0) @@ -100,7 +100,7 @@ def print_history(self): if self.history is None: self.update_history() - self.print_list(self.history[::-1], format_str%( _("Date"), _("Description"), _("Amount"), _("Balance"))) + self.print_list(self.history[::-1], format_str%(_("Date"), _("Description"), _("Amount"), _("Balance"))) def update_history(self): width = [20, 40, 14, 14] @@ -141,10 +141,10 @@ def print_balance(self): else: msg = _("Not connected") - self.stdscr.addstr( self.maxy -1, 3, msg) + self.stdscr.addstr(self.maxy -1, 3, msg) for i in range(self.num_tabs): - self.stdscr.addstr( 0, 2 + 2*i + len(''.join(self.tab_names[0:i])), ' '+self.tab_names[i]+' ', curses.A_BOLD if self.tab == i else 0) + self.stdscr.addstr(0, 2 + 2*i + len(''.join(self.tab_names[0:i])), ' '+self.tab_names[i]+' ', curses.A_BOLD if self.tab == i else 0) self.stdscr.addstr(self.maxy -1, self.maxx-30, ' '.join([_("Settings"), _("Network"), _("Quit")])) @@ -165,9 +165,9 @@ def print_addresses(self): self.print_list(messages, fmt % ("Address", "Label")) def print_edit_line(self, y, label, text, index, size): - text += " "*(size - len(text) ) - self.stdscr.addstr( y, 2, label) - self.stdscr.addstr( y, 15, text, curses.A_REVERSE if self.pos%6==index else curses.color_pair(1)) + text += " "*(size - len(text)) + self.stdscr.addstr(y, 2, label) + self.stdscr.addstr(y, 15, text, curses.A_REVERSE if self.pos%6==index else curses.color_pair(1)) def print_send_tab(self): self.stdscr.clear() @@ -175,8 +175,8 @@ def print_send_tab(self): self.print_edit_line(5, _("Description"), self.str_description, 1, 40) self.print_edit_line(7, _("Amount"), self.str_amount, 2, 15) self.print_edit_line(9, _("Fee"), self.str_fee, 3, 15) - self.stdscr.addstr( 12, 15, _("[Send]"), curses.A_REVERSE if self.pos%6==4 else curses.color_pair(2)) - self.stdscr.addstr( 12, 25, _("[Clear]"), curses.A_REVERSE if self.pos%6==5 else curses.color_pair(2)) + self.stdscr.addstr(12, 15, _("[Send]"), curses.A_REVERSE if self.pos%6==4 else curses.color_pair(2)) + self.stdscr.addstr(12, 25, _("[Clear]"), curses.A_REVERSE if self.pos%6==5 else curses.color_pair(2)) self.maxpos = 6 def print_banner(self): @@ -206,13 +206,13 @@ def print_list(self, lst, firstline = None): if not self.maxpos: return if firstline: firstline += " "*(self.maxx -2 - len(firstline)) - self.stdscr.addstr( 1, 1, firstline ) + self.stdscr.addstr(1, 1, firstline) for i in range(self.maxy-4): msg = lst[i] if i < len(lst) else "" msg += " "*(self.maxx - 2 - len(msg)) m = msg[0:self.maxx - 2] m = m.encode(self.encoding) - self.stdscr.addstr( i+2, 1, m, curses.A_REVERSE if i == (self.pos % self.maxpos) else 0) + self.stdscr.addstr(i+2, 1, m, curses.A_REVERSE if i == (self.pos % self.maxpos) else 0) def refresh(self): if self.tab == -1: return @@ -401,7 +401,7 @@ def network_dialog(self): def settings_dialog(self): fee = str(PyDecimal(self.config.fee_per_kb()) / COIN) out = self.run_dialog('Settings', [ - {'label':'Default fee', 'type':'satoshis', 'value': fee } + {'label':'Default fee', 'type':'satoshis', 'value': fee} ], buttons = 1) if out: if out.get('Default fee'): @@ -419,13 +419,13 @@ def password_dialog(self): def run_dialog(self, title, items, interval=2, buttons=None, y_pos=3): self.popup_pos = 0 - self.w = curses.newwin( 5 + len(list(items))*interval + (2 if buttons else 0), 50, y_pos, 5) + self.w = curses.newwin(5 + len(list(items))*interval + (2 if buttons else 0), 50, y_pos, 5) w = self.w out = {} while True: w.clear() w.border(0) - w.addstr( 0, 2, title) + w.addstr(0, 2, title) num = len(list(items)) @@ -451,14 +451,14 @@ def run_dialog(self, title, items, interval=2, buttons=None, y_pos=3): value += ' '*(20-len(value)) if 'value' in item: - w.addstr( 2+interval*i, 2, label) - w.addstr( 2+interval*i, 15, value, curses.A_REVERSE if self.popup_pos%numpos==i else curses.color_pair(1) ) + w.addstr(2+interval*i, 2, label) + w.addstr(2+interval*i, 15, value, curses.A_REVERSE if self.popup_pos%numpos==i else curses.color_pair(1)) else: - w.addstr( 2+interval*i, 2, label, curses.A_REVERSE if self.popup_pos%numpos==i else 0) + w.addstr(2+interval*i, 2, label, curses.A_REVERSE if self.popup_pos%numpos==i else 0) if buttons: - w.addstr( 5+interval*i, 10, "[ ok ]", curses.A_REVERSE if self.popup_pos%numpos==(numpos-2) else curses.color_pair(2)) - w.addstr( 5+interval*i, 25, "[cancel]", curses.A_REVERSE if self.popup_pos%numpos==(numpos-1) else curses.color_pair(2)) + w.addstr(5+interval*i, 10, "[ ok ]", curses.A_REVERSE if self.popup_pos%numpos==(numpos-2) else curses.color_pair(2)) + w.addstr(5+interval*i, 25, "[cancel]", curses.A_REVERSE if self.popup_pos%numpos==(numpos-1) else curses.color_pair(2)) w.refresh() diff --git a/electroncash_plugins/hw_wallet/cmdline.py b/electroncash_plugins/hw_wallet/cmdline.py index 83c1929217a5..114348bfbd51 100644 --- a/electroncash_plugins/hw_wallet/cmdline.py +++ b/electroncash_plugins/hw_wallet/cmdline.py @@ -8,7 +8,7 @@ def get_passphrase(self, msg, confirm): return getpass.getpass('') def get_pin(self, msg): - t = { 'a':'7', 'b':'8', 'c':'9', 'd':'4', 'e':'5', 'f':'6', 'g':'1', 'h':'2', 'i':'3'} + t = {'a':'7', 'b':'8', 'c':'9', 'd':'4', 'e':'5', 'f':'6', 'g':'1', 'h':'2', 'i':'3'} print_msg(msg) print_msg("a b c\nd e f\ng h i\n-----") o = raw_input() diff --git a/electroncash_plugins/keepkey/keepkey.py b/electroncash_plugins/keepkey/keepkey.py index cb631271cf73..9abdcb1dbf51 100644 --- a/electroncash_plugins/keepkey/keepkey.py +++ b/electroncash_plugins/keepkey/keepkey.py @@ -312,16 +312,16 @@ def get_xpub(self, device_id, derivation, xtype, wizard): return xpub def get_keepkey_input_script_type(self, electrum_txin_type: str): - if electrum_txin_type in ('p2pkh', ): + if electrum_txin_type in ('p2pkh',): return self.types.SPENDADDRESS - if electrum_txin_type in ('p2sh', ): + if electrum_txin_type in ('p2sh',): return self.types.SPENDMULTISIG raise ValueError('unexpected txin type: {}'.format(electrum_txin_type)) def get_keepkey_output_script_type(self, electrum_txin_type: str): - if electrum_txin_type in ('p2pkh', ): + if electrum_txin_type in ('p2pkh',): return self.types.PAYTOADDRESS - if electrum_txin_type in ('p2sh', ): + if electrum_txin_type in ('p2sh',): return self.types.PAYTOMULTISIG raise ValueError('unexpected txin type: {}'.format(electrum_txin_type)) diff --git a/electroncash_plugins/ledger/auth2fa.py b/electroncash_plugins/ledger/auth2fa.py index d7a8a5f0d892..e2bc13786e30 100644 --- a/electroncash_plugins/ledger/auth2fa.py +++ b/electroncash_plugins/ledger/auth2fa.py @@ -15,7 +15,7 @@ "put your cursor into it, and plug your device into that computer. " \ "It will output a summary of the transaction being signed and a one-time PIN.

" \ "Verify the transaction summary and type the PIN code here.

" \ - "Before pressing enter, plug the device back into this computer.
" ), + "Before pressing enter, plug the device back into this computer.
"), _("Verify the address below.
Type the character from your security card corresponding to the BOLD character.") ] @@ -150,7 +150,7 @@ def update_dlg(self): def getDevice2FAMode(self): apdu = [0xe0, 0x24, 0x01, 0x00, 0x00, 0x01] # get 2fa mode try: - mode = self.dongle.exchange( bytearray(apdu) ) + mode = self.dongle.exchange(bytearray(apdu)) return mode except BTChipException as e: print_error('Device getMode Failed') diff --git a/electroncash_plugins/ledger/ledger.py b/electroncash_plugins/ledger/ledger.py index 5d2b5a7e3dec..6ab90db4164c 100644 --- a/electroncash_plugins/ledger/ledger.py +++ b/electroncash_plugins/ledger/ledger.py @@ -180,7 +180,7 @@ def perform_hw1_preflight(self): except BTChipException as e: if (e.sw == 0x6985): self.dongleObject.dongle.close() - self.handler.get_setup( ) + self.handler.get_setup() # Acquire the new client on the next run else: raise e From 65fe3b67de9a44d1c4dffa0180968b82eff98d09 Mon Sep 17 00:00:00 2001 From: agilewalker Date: Mon, 29 Mar 2021 11:45:20 +0800 Subject: [PATCH 027/208] Fix #2211 --- electroncash_gui/qt/main_window.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/electroncash_gui/qt/main_window.py b/electroncash_gui/qt/main_window.py index 24b3b2d9497d..72e7d67c5059 100644 --- a/electroncash_gui/qt/main_window.py +++ b/electroncash_gui/qt/main_window.py @@ -2887,10 +2887,10 @@ def create_status_bar(self): self.addr_converter_button.setHidden(self.gui_object.is_cashaddr_status_button_hidden()) self.gui_object.cashaddr_status_button_hidden_signal.connect(self.addr_converter_button.setHidden) - q_icon_prefs = QIcon(":icons/preferences.svg"), _("Preferences"), self.settings_dialog) - sb.addPermanentWidget(StatusBarButton(q_icon_prefs)) - q_icon_seed = QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog) - self.seed_button = StatusBarButton(q_icon_seed) + q_icon_prefs = QIcon(":icons/preferences.svg"), _("Preferences"), self.settings_dialog + sb.addPermanentWidget(StatusBarButton(*q_icon_prefs)) + q_icon_seed = QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog + self.seed_button = StatusBarButton(*q_icon_seed) sb.addPermanentWidget(self.seed_button) weakSelf = Weak.ref(self) gui_object = self.gui_object From e2eee6d5f75db063eefa25d3ddd900d85ee86b8f Mon Sep 17 00:00:00 2001 From: Georg Engelmann Date: Mon, 29 Mar 2021 14:01:56 +0200 Subject: [PATCH 028/208] Add electrs.electroncash.de (#2222) * Add electrs.electroncash.de * Update electroncash/servers.json Co-authored-by: Calin Culianu --- electroncash/servers.json | 5 +++++ electroncash/servers_testnet.json | 3 +++ 2 files changed, 8 insertions(+) diff --git a/electroncash/servers.json b/electroncash/servers.json index a876613cc846..5db92e0db58d 100644 --- a/electroncash/servers.json +++ b/electroncash/servers.json @@ -116,6 +116,11 @@ "t": "50001", "version": "1.4" }, + "electrs.electroncash.de": { + "pruning": "-", + "s": "40002", + "version": "1.4" + }, "jktsologn7uprtwn7gsgmwuddj6rxsqmwc2vaug7jwcwzm2bxqnfpwad.onion": { "pruning": "-", "s": "50002", diff --git a/electroncash/servers_testnet.json b/electroncash/servers_testnet.json index 8c394a34820b..024edb17b166 100644 --- a/electroncash/servers_testnet.json +++ b/electroncash/servers_testnet.json @@ -24,6 +24,9 @@ "s": "50004", "t": "50003" }, + "electrs.electroncash.de": { + "s": "60002" + }, "tbch.loping.net": { "s": "60002", "t": "60001" From b711942b3b14c17c3f5742c606ea55757cc05812 Mon Sep 17 00:00:00 2001 From: Daniel Gonzalez Gasull Date: Fri, 2 Apr 2021 06:03:48 +0000 Subject: [PATCH 029/208] Removed unreachable line of code This line was introduced in the original implementation of the function back in 2015, and never cleaned up: e8d30129eae10b5efeaa9e95a900576e41dd2bf1 --- electroncash/rsakey.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/electroncash/rsakey.py b/electroncash/rsakey.py index 205dfa8a83d3..2a2c25a84b91 100644 --- a/electroncash/rsakey.py +++ b/electroncash/rsakey.py @@ -125,7 +125,7 @@ def numBits(n): '8':4, '9':4, 'a':4, 'b':4, 'c':4, 'd':4, 'e':4, 'f':4, }[s[0]] - return int(math.floor(math.log(n, 2))+1) + def numBytes(n): if n==0: From 8aeff33b6186e47641aabc45c425a01b7f390379 Mon Sep 17 00:00:00 2001 From: Axel Gembe Date: Sat, 3 Apr 2021 17:44:51 +0700 Subject: [PATCH 030/208] OpenSSL: Update to version 1.1.1k This has some important fixes: * Fixed a problem with verifying a certificate chain when using the X509_V_FLAG_X509_STRICT flag. This flag enables additional security checks of the certificates present in a certificate chain. It is not set by default. ([CVE-2021-3450]) * Fixed an issue where an OpenSSL TLS server may crash if sent a maliciously crafted renegotiation ClientHello message from a client. If a TLSv1.2 renegotiation ClientHello omits the signature_algorithms extension (where it was present in the initial ClientHello), but includes a signature_algorithms_cert extension then a NULL pointer dereference will result, leading to a crash and a denial of service attack. ([CVE-2021-3449]) AFAICT we are not affected by CVE-2021-3450, but the Fusion/Shuffle server is affected by CVE-2021-3449. --- contrib/openssl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/openssl b/contrib/openssl index 52c587d60be6..fd78df59b0f6 160000 --- a/contrib/openssl +++ b/contrib/openssl @@ -1 +1 @@ -Subproject commit 52c587d60be67c337364b830dd3fdc15404a2f04 +Subproject commit fd78df59b0f656aefe96e39533130454aa957c00 From 17269ae2e680a601c4e6a1e9e5dd8bc6e5f1b656 Mon Sep 17 00:00:00 2001 From: Axel Gembe Date: Sat, 3 Apr 2021 17:56:48 +0700 Subject: [PATCH 031/208] Python: Update to version 3.8.9 This has an important fix: * bpo-43631: high-severity CVE-2021-3449 and CVE-2021-3450 were published for OpenSSL, it's been upgraded to 1.1.1k in CI, and macOS and Windows installers. AFAICT we are not affected by CVE-2021-3450, but the Fusion/Shuffle server is affected by CVE-2021-3449. --- contrib/base.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contrib/base.sh b/contrib/base.sh index 4705a6b48870..6066b80b950f 100644 --- a/contrib/base.sh +++ b/contrib/base.sh @@ -194,8 +194,8 @@ export PYTHONHASHSEED=22 export SOURCE_DATE_EPOCH=1530212462 # Note, when upgrading Python, check the Windows python.exe embedded manifest for changes. # If the manifest changed, contrib/build-wine/manifest.xml needs to be updated. -export PYTHON_VERSION=3.8.7 # Windows, OSX & Linux AppImage use this to determine what to download/build -export PYTHON_SRC_TARBALL_HASH="ddcc1df16bb5b87aa42ec5d20a5b902f2d088caa269b28e01590f97a798ec50a" # If you change PYTHON_VERSION above, update this by downloading the tarball manually and doing a sha256sum on it. +export PYTHON_VERSION=3.8.9 # Windows, OSX & Linux AppImage use this to determine what to download/build +export PYTHON_SRC_TARBALL_HASH="5e391f3ec45da2954419cab0beaefd8be38895ea5ce33577c3ec14940c4b9572" # If you change PYTHON_VERSION above, update this by downloading the tarball manually and doing a sha256sum on it. export DEFAULT_GIT_REPO=https://github.com/Electron-Cash/Electron-Cash if [ -z "$GIT_REPO" ] ; then # If no override from env is present, use default. Support for overrides From 079eaa0aa0f20f576486ed8e73472948b5e4f7bd Mon Sep 17 00:00:00 2001 From: Kevin Nowaczyk Date: Sat, 3 Apr 2021 10:51:08 -0400 Subject: [PATCH 032/208] fixed typo --- ios/README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ios/README.rst b/ios/README.rst index 3aca081e7665..12a3720023d6 100644 --- a/ios/README.rst +++ b/ios/README.rst @@ -22,7 +22,7 @@ Quick Start Instructions python3 -m pip install 'cookiecutter==1.6.0' --user --upgrade python3 -m pip install 'briefcase==0.2.6' --user --upgrade python3 -m pip install 'pbxproj==2.5.1' --user --upgrade - pyrhon3 -m pip install 'rubicon-objc==0.2.10' --user --upgrade + python3 -m pip install 'rubicon-objc==0.2.10' --user --upgrade (NOTE: The exact versions specified above are known to work, but you may also try and use newer version as well.) From fea82b9b29904b0da2d6c1912f93d968cdcfee50 Mon Sep 17 00:00:00 2001 From: Axel Gembe Date: Mon, 5 Apr 2021 12:57:58 +0700 Subject: [PATCH 033/208] AppImage: Update OpenSSL to version 1.1.1-1ubuntu2.1~18.04.9 This has some important fixes: * SECURITY UPDATE: NULL pointer deref in signature_algorithms processing - debian/patches/CVE-2021-3449-1.patch: fix NULL pointer dereference in ssl/statem/extensions.c. - debian/patches/CVE-2021-3449-2.patch: teach TLSProxy how to encrypt <= TLSv1.2 ETM records in util/perl/TLSProxy/Message.pm. - debian/patches/CVE-2021-3449-3.patch: add a test to test/recipes/70-test_renegotiation.t. - debian/patches/CVE-2021-3449-4.patch: ensure buffer/length pairs are always in sync in ssl/s3_lib.c, ssl/ssl_lib.c, ssl/statem/extensions.c, ssl/statem/extensions_clnt.c, ssl/statem/statem_clnt.c, ssl/statem/statem_srvr.c. - CVE-2021-3449 This also updates libudev-dev to version 237-3ubuntu10.45, the old version is not available anymore. --- contrib/build-linux/appimage/Dockerfile_ub1804 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contrib/build-linux/appimage/Dockerfile_ub1804 b/contrib/build-linux/appimage/Dockerfile_ub1804 index b03ba0c1efb8..953fbb35d856 100644 --- a/contrib/build-linux/appimage/Dockerfile_ub1804 +++ b/contrib/build-linux/appimage/Dockerfile_ub1804 @@ -21,7 +21,7 @@ RUN echo deb ${UBUNTU_MIRROR} bionic main restricted universe multiverse > /etc/ libncurses5-dev=6.1-1ubuntu1.18.04 \ libsqlite3-dev=3.22.0-1ubuntu0.4 \ libusb-1.0-0-dev=2:1.0.21-2 \ - libudev-dev=237-3ubuntu10.44 \ + libudev-dev=237-3ubuntu10.45 \ gettext=0.19.8.1-6ubuntu0.3 \ pkg-config=0.29.1-0ubuntu2 \ libdbus-1-3=1.12.2-1ubuntu1.2 \ @@ -34,7 +34,7 @@ RUN echo deb ${UBUNTU_MIRROR} bionic main restricted universe multiverse > /etc/ zlib1g-dev=1:1.2.11.dfsg-0ubuntu2 \ libfreetype6=2.8.1-2ubuntu2.1 \ libfontconfig1=2.12.6-0ubuntu2 \ - libssl-dev=1.1.1-1ubuntu2.1~18.04.8 \ + libssl-dev=1.1.1-1ubuntu2.1~18.04.9 \ rustc=1.47.0+dfsg1+llvm-1ubuntu1~18.04.1 \ && \ rm -rf /var/lib/apt/lists/* && \ From 3c050b12fe483327d162a1b50010ac0b6470cdd7 Mon Sep 17 00:00:00 2001 From: Daniel Gonzalez Gasull Date: Tue, 6 Apr 2021 02:07:44 +0000 Subject: [PATCH 034/208] Remove unreachable line of code --- electroncash/wallet.py | 1 - 1 file changed, 1 deletion(-) diff --git a/electroncash/wallet.py b/electroncash/wallet.py index 0a7701f01423..d6bbe1e75283 100644 --- a/electroncash/wallet.py +++ b/electroncash/wallet.py @@ -1976,7 +1976,6 @@ def make_unsigned_transaction(self, inputs, outputs, config, fixed_fee=None, cha sats_per_byte=fee_in_satoshis/tx_in_bytes if (sats_per_byte > 50): raise ExcessiveFee() - return # Sort the inputs and outputs deterministically tx.BIP_LI01_sort() From 5e1cd78340e3abdd506f73e03bb7dd2e81fa9d26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aldin=20Kova=C4=8Devi=C4=87?= Date: Thu, 8 Apr 2021 18:45:56 +0200 Subject: [PATCH 035/208] Android: Added initial UI for multisg wallet creation. --- .../electroncash/electroncash3/NewWallet.kt | 61 ++++++++++++++- .../main/res/layout/multisig_cosigners.xml | 75 +++++++++++++++++++ 2 files changed, 135 insertions(+), 1 deletion(-) create mode 100644 android/app/src/main/res/layout/multisig_cosigners.xml diff --git a/android/app/src/main/java/org/electroncash/electroncash3/NewWallet.kt b/android/app/src/main/java/org/electroncash/electroncash3/NewWallet.kt index bd5dc0c53803..b062e7673d1e 100644 --- a/android/app/src/main/java/org/electroncash/electroncash3/NewWallet.kt +++ b/android/app/src/main/java/org/electroncash/electroncash3/NewWallet.kt @@ -5,6 +5,7 @@ import android.content.Intent import android.os.Bundle import android.text.Selection import android.view.View +import android.widget.SeekBar import android.widget.Toast import androidx.appcompat.app.AlertDialog import androidx.fragment.app.DialogFragment @@ -12,6 +13,7 @@ import com.chaquo.python.Kwarg import com.chaquo.python.PyException import com.google.zxing.integration.android.IntentIntegrator import kotlinx.android.synthetic.main.choose_keystore.* +import kotlinx.android.synthetic.main.multisig_cosigners.* import kotlinx.android.synthetic.main.wallet_new.* import kotlinx.android.synthetic.main.wallet_new_2.* import kotlin.properties.Delegates.notNull @@ -53,7 +55,8 @@ class NewWalletDialog1 : AlertDialogFragment() { showDialog(this, nextDialog.apply { setArguments(arguments) }) } R.id.menuMultisigWallet -> { - // TODO + nextDialog = CosignerDialog() + showDialog(this, nextDialog.apply { setArguments(arguments) }) } else -> { throw Exception("Unknown item: ${spnWalletKind.selectedItem}") @@ -345,6 +348,62 @@ fun setupSeedDialog(fragment: AlertDialogFragment) { } } +// Choose the number of multi-sig wallet cosigners +class CosignerDialog : AlertDialogFragment() { + override fun onBuildDialog(builder: AlertDialog.Builder) { + builder.setTitle(R.string.New_wallet) + .setView(R.layout.multisig_cosigners) + .setPositiveButton(R.string.next, null) + .setNegativeButton(R.string.cancel, null) + } + + override fun onShowDialog() { + super.onShowDialog() + + msFromText.text = getString(R.string.from_cosigners).replace("%d", "2") + msReqText.text = getString(R.string.require_signatures).replace("%d", "2") + + // Handle number of cosigners + msFrom?.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener { + override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) { + msFromText.text = getString(R.string.from_cosigners).replace("%d", progress.toString()) + msReq.max = progress + + if (progress == 0) { + msFrom.progress = 1 + } + } + + override fun onStartTrackingTouch(seekBar: SeekBar?) { + + } + + override fun onStopTrackingTouch(seekBar: SeekBar) { + + } + + }) + + // Handle number of required signatures + msReq?.setOnSeekBarChangeListener(object: SeekBar.OnSeekBarChangeListener { + override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) { + msReqText.text = getString(R.string.require_signatures).replace("%d", progress.toString()) + + if (progress == 0) { + msReq.progress = 1 + } + } + + override fun onStartTrackingTouch(seekBar: SeekBar?) { + } + + override fun onStopTrackingTouch(seekBar: SeekBar?) { + } + + }) + } +} + fun seedAdvice(seed: String): String { return app.getString(R.string.please_save, seed.split(" ").size) + " " + diff --git a/android/app/src/main/res/layout/multisig_cosigners.xml b/android/app/src/main/res/layout/multisig_cosigners.xml new file mode 100644 index 000000000000..81205b8f920c --- /dev/null +++ b/android/app/src/main/res/layout/multisig_cosigners.xml @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + \ No newline at end of file From 6d5f101511087f9e993c578725d67dc8ccf3f2b1 Mon Sep 17 00:00:00 2001 From: Axel Gembe Date: Fri, 9 Apr 2021 13:55:49 +0700 Subject: [PATCH 036/208] Build: Verify the Python checksums Before this change, whoever has a copy of the Python release signing key is a trusted third party. This commit changes this so that in addition to checking the signature of the installers, the checksums are also verified. This is motivated by the Python 3.8.9 release (2021-04-02) being replaced with a different version (2021-04-06) that didn't match the PGP signature. --- contrib/build-wine/_build.sh | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/contrib/build-wine/_build.sh b/contrib/build-wine/_build.sh index f100e9f2c8ad..3f9adef3e2af 100755 --- a/contrib/build-wine/_build.sh +++ b/contrib/build-wine/_build.sh @@ -89,13 +89,29 @@ prepare_wine() { "$here"/pgp/7ed10b6531d7c8e1bc296021fc624643487034e5.asc \ || fail "Failed to import Python release signing keys" - info "Installing Python ..." # Install Python - for msifile in core dev exe lib pip tools; do - info "Installing $msifile..." + info "Installing Python ..." + msifiles="core dev exe lib pip tools" + + for msifile in $msifiles ; do + info "Downloading $msifile..." wget "https://www.python.org/ftp/python/$PYTHON_VERSION/win32/${msifile}.msi" wget "https://www.python.org/ftp/python/$PYTHON_VERSION/win32/${msifile}.msi.asc" verify_signature "${msifile}.msi.asc" $KEYRING_PYTHON_DEV + done + + $SHA256_PROG -c - << EOF +934eda542020cb5ba6de16f5c150e7571a25dd3cb9f3832eb8b2cb54c7331aa6 core.msi +58e517f0bb55fbf9ba1d9ec6a17a5420959469001ec1945c6c56dbbe566d3056 dev.msi +34594d5522b0939b50edfdb01a980589f559fc659ce1f20288d297dda0905630 exe.msi +fcd8bd0ead0a67906f39e99e557d66e71bc6810bd4916d03c2c5341ff95992bf lib.msi +8d8159fdb9f42b81e9206bf7be0c2b034b3df717d58cfb484c0b3fb8ec3cab6a pip.msi +ca8ef45591b07e7dbcc9b4b7b1c4d6528d3f0349fdf6779c71312bd9c3187801 tools.msi +EOF + test $? -eq 0 || fail "Failed to verify Python checksums" + + for msifile in $msifiles ; do + info "Installing $msifile..." wine msiexec /i "${msifile}.msi" /qn TARGETDIR=$PYHOME || fail "Failed to install Python component: ${msifile}" done From e286a8a66494ff3f944d3b5984224e3018d02c02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aldin=20Kova=C4=8Devi=C4=87?= Date: Fri, 9 Apr 2021 23:21:41 +0200 Subject: [PATCH 037/208] Android: Added the layout for displaying the master key. --- .../src/main/res/layout/show_master_key.xml | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 android/app/src/main/res/layout/show_master_key.xml diff --git a/android/app/src/main/res/layout/show_master_key.xml b/android/app/src/main/res/layout/show_master_key.xml new file mode 100644 index 000000000000..7a8da6c556a6 --- /dev/null +++ b/android/app/src/main/res/layout/show_master_key.xml @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + \ No newline at end of file From 73ec4a6aad2975688d159d3bafe7fae33d4a939d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aldin=20Kova=C4=8Devi=C4=87?= Date: Fri, 9 Apr 2021 23:22:18 +0200 Subject: [PATCH 038/208] Android: Updated the keystore dialog text. --- android/app/src/main/res/layout/choose_keystore.xml | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/android/app/src/main/res/layout/choose_keystore.xml b/android/app/src/main/res/layout/choose_keystore.xml index 32707bc19539..2ac3829be8cb 100644 --- a/android/app/src/main/res/layout/choose_keystore.xml +++ b/android/app/src/main/res/layout/choose_keystore.xml @@ -11,12 +11,14 @@ android:layout_height="wrap_content"> @@ -27,8 +29,8 @@ android:layout_marginTop="12dp" android:layout_marginEnd="24dp" app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="@+id/textView8" - app:layout_constraintTop_toBottomOf="@+id/textView8" /> + app:layout_constraintStart_toStartOf="@+id/keystoreDesc" + app:layout_constraintTop_toBottomOf="@+id/keystoreDesc" /> Date: Fri, 9 Apr 2021 23:23:06 +0200 Subject: [PATCH 039/208] Android: Added the menu for cosigner keystores. --- android/app/src/main/res/menu/cosigner_type.xml | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 android/app/src/main/res/menu/cosigner_type.xml diff --git a/android/app/src/main/res/menu/cosigner_type.xml b/android/app/src/main/res/menu/cosigner_type.xml new file mode 100644 index 000000000000..e22a2a700de7 --- /dev/null +++ b/android/app/src/main/res/menu/cosigner_type.xml @@ -0,0 +1,11 @@ + + + + + + + \ No newline at end of file From d68c1fd961b3e35a816f2b016584162f93e69606 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aldin=20Kova=C4=8Devi=C4=87?= Date: Fri, 9 Apr 2021 23:26:02 +0200 Subject: [PATCH 040/208] Android: Added multisig wallet creation logic to console.py. --- .../electroncash_gui/android/console.py | 29 ++++++++++++++++--- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/android/app/src/main/python/electroncash_gui/android/console.py b/android/app/src/main/python/electroncash_gui/android/console.py index 3ab067493d67..30d2cbce2620 100644 --- a/android/app/src/main/python/electroncash_gui/android/console.py +++ b/android/app/src/main/python/electroncash_gui/android/console.py @@ -11,7 +11,7 @@ from electroncash.i18n import _ from electroncash.storage import WalletStorage from electroncash.wallet import (ImportedAddressWallet, ImportedPrivkeyWallet, Standard_Wallet, - Wallet) + Wallet, Multisig_Wallet) CALLBACKS = ["banner", "blockchain_updated", "fee", "interfaces", "new_transaction", @@ -135,7 +135,7 @@ def close_wallet(self, name=None): self.daemon.stop_wallet(self._wallet_path(name)) def create(self, name, password, seed=None, passphrase="", bip39_derivation=None, - master=None, addresses=None, privkeys=None): + master=None, addresses=None, privkeys=None, multisig=False): """Create or restore a new wallet""" path = self._wallet_path(name) if exists(path): @@ -158,11 +158,32 @@ def create(self, name, password, seed=None, passphrase="", bip39_derivation=None print("Your wallet generation seed is:\n\"%s\"" % seed) ks = keystore.from_seed(seed, passphrase) - storage.put('keystore', ks.dump()) - wallet = Standard_Wallet(storage) + if not multisig: + storage.put('keystore', ks.dump()) + wallet = Standard_Wallet(storage) + else: # for multisig, we need to get the keystore + return ks wallet.update_password(None, password, encrypt=True) + def create_multisig(self, name, password, keystores=None, cosigners=None, signatures=None): + """Create or restore a new wallet""" + path = self._wallet_path(name) + if exists(path): + raise FileExistsError(path) + storage = WalletStorage(path) + + # Multisig wallet type + storage.put("wallet_type", "%dof%d" % (signatures, cosigners)) + i = 0 + for k in keystores: + storage.put('x%d/'%(i+1), k.dump()) + i += 1 + storage.write() + + wallet = Multisig_Wallet(storage) + wallet.update_password(None, password, encrypt=True) + # END commands from the argparse interface. # BEGIN commands which only exist here. From a626e64baf5ca08408d1bd35cae035fe644acfb0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aldin=20Kova=C4=8Devi=C4=87?= Date: Fri, 9 Apr 2021 23:27:46 +0200 Subject: [PATCH 041/208] Android: Implemented several new dialogs and reworked logic to support multisig wallet creation. --- .../electroncash/electroncash3/NewWallet.kt | 273 +++++++++++++++--- 1 file changed, 238 insertions(+), 35 deletions(-) diff --git a/android/app/src/main/java/org/electroncash/electroncash3/NewWallet.kt b/android/app/src/main/java/org/electroncash/electroncash3/NewWallet.kt index b062e7673d1e..c7bccdb932e4 100644 --- a/android/app/src/main/java/org/electroncash/electroncash3/NewWallet.kt +++ b/android/app/src/main/java/org/electroncash/electroncash3/NewWallet.kt @@ -1,27 +1,36 @@ package org.electroncash.electroncash3 import android.app.Dialog -import android.content.Intent +import android.content.* import android.os.Bundle +import android.text.InputType import android.text.Selection +import android.util.Log +import android.view.LayoutInflater import android.view.View +import android.view.ViewGroup import android.widget.SeekBar import android.widget.Toast import androidx.appcompat.app.AlertDialog import androidx.fragment.app.DialogFragment +import androidx.fragment.app.Fragment import com.chaquo.python.Kwarg import com.chaquo.python.PyException +import com.chaquo.python.PyObject import com.google.zxing.integration.android.IntentIntegrator import kotlinx.android.synthetic.main.choose_keystore.* import kotlinx.android.synthetic.main.multisig_cosigners.* +import kotlinx.android.synthetic.main.show_master_key.* import kotlinx.android.synthetic.main.wallet_new.* import kotlinx.android.synthetic.main.wallet_new_2.* +import java.security.KeyStore import kotlin.properties.Delegates.notNull val libKeystore by lazy { libMod("keystore") } val libWallet by lazy { libMod("wallet") } +val keystores: ArrayList = ArrayList() class NewWalletDialog1 : AlertDialogFragment() { override fun onBuildDialog(builder: AlertDialog.Builder) { @@ -33,7 +42,6 @@ class NewWalletDialog1 : AlertDialogFragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - // spnType.adapter = MenuAdapter(context!!, R.menu.wallet_type) spnWalletKind.adapter = MenuAdapter(context!!, R.menu.wallet_kind) } @@ -66,8 +74,23 @@ class NewWalletDialog1 : AlertDialogFragment() { } catch (e: ToastException) { e.show() } } } + + override fun onDismiss(dialog: DialogInterface) { + super.onDismiss(dialog) + keystores.clear() + } } +fun closeDialogs(targetFragment: Fragment) { + + if (targetFragment.targetFragment == null) { + (targetFragment as DialogFragment).dismiss() + return + } + + closeDialogs(targetFragment.targetFragment!!) + (targetFragment as DialogFragment).dismiss() +} fun validateWalletName(name: String) { if (name.isEmpty()) { @@ -98,23 +121,39 @@ fun confirmPassword(dialog: Dialog): String { } // Choose the way of generating the wallet (new seed, import seed, etc.) -class KeystoreDialog : TaskLauncherDialog() { +class KeystoreDialog : AlertDialogFragment() { var input: String by notNull() override fun onBuildDialog(builder: AlertDialog.Builder) { - builder.setTitle(R.string.New_wallet) + builder.setTitle(R.string.keystore) .setView(R.layout.choose_keystore) .setPositiveButton(android.R.string.ok, null) .setNegativeButton(R.string.back, null) } - override fun doInBackground(): String { - TODO("Not yet implemented") - } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - spnType.adapter = MenuAdapter(context!!, R.menu.wallet_type) + + /* Choose the appropriate keystore dropdown, based on wallet type */ + val multisig = arguments!!.getBoolean("multisig") + val currentCosigner = arguments!!.getInt("i_signer") // + val numOfCosigners = arguments!!.getInt("cosigners") + + /* Handle dialog title for cosigners */ + if (multisig) { + dialog.setTitle(getString(R.string.Add_cosigner) + + " (${currentCosigner} of ${numOfCosigners})") + } + + val keystoreMenu: Int + if (multisig && currentCosigner != 1) { + keystoreMenu = R.menu.cosigner_type + keystoreDesc.setText(R.string.add_a) + } else { + keystoreMenu = R.menu.wallet_type + } + + spnType.adapter = MenuAdapter(context!!, keystoreMenu) } override fun onShowDialog() { @@ -123,8 +162,7 @@ class KeystoreDialog : TaskLauncherDialog() { val nextDialog: DialogFragment val keystoreType = spnType.selectedItemId.toInt() - - if (keystoreType in listOf(R.id.menuCreateSeed, R.id.menuRestoreSeed)) { + if (keystoreType in listOf(R.id.menuCreateSeed, R.id.menuRestoreSeed, R.id.menuCosignerSeed)) { nextDialog = NewWalletSeedDialog() val seed = if (keystoreType == R.id.menuCreateSeed) daemonModel.commands.callAttr("make_seed").toString() @@ -132,7 +170,7 @@ class KeystoreDialog : TaskLauncherDialog() { arguments!!.putString("seed", seed) } else if (keystoreType == R.id.menuImport) { nextDialog = NewWalletImportDialog() - } else if (keystoreType == R.id.menuImportMaster) { + } else if (keystoreType in listOf(R.id.menuImportMaster, R.id.menuCosignerKey)) { nextDialog = NewWalletImportMasterDialog() } else { throw Exception("Unknown item: ${spnType.selectedItem}") @@ -141,6 +179,10 @@ class KeystoreDialog : TaskLauncherDialog() { showDialog(this, nextDialog) } catch (e: ToastException) { e.show() } } + +// dialog.getButton(AlertDialog.BUTTON_NEGATIVE).setOnClickListener { +// arguments = resetCosigner(arguments) +// } } } @@ -162,17 +204,79 @@ abstract class NewWalletDialog2 : TaskLauncherDialog() { val name = arguments!!.getString("name")!! val password = arguments!!.getString("password")!! onCreateWallet(name, password) - daemonModel.loadWallet(name, password) + + val multisig = arguments!!.getBoolean("multisig") + + /** + * For multisig wallets, wait until all cosigners have been added, + * and then create and load the multisig wallet. + * + * Otherwise, load the created wallet. + */ + if (multisig) { + val currentCosigner = arguments!!.getInt("i_signer") + val numCosigners = arguments!!.getInt("cosigners") + val numSignatures = arguments!!.getInt("signatures") + + // Close the previous cosigner's keystore dialog. + // TODO: Consider a better solution for closing dialogs? + (targetFragment as KeystoreDialog).dismiss() + + if (currentCosigner == numCosigners) { + daemonModel.commands.callAttr( + "create_multisig", name, password, + Kwarg("keystores", keystores.toArray()), + Kwarg("cosigners", numCosigners), + Kwarg("signatures", numSignatures) + ) + daemonModel.loadWallet(name, password) + closeDialogs(targetFragment!!) + } + } else { + daemonModel.loadWallet(name, password) + } + return name } abstract fun onCreateWallet(name: String, password: String) override fun onPostExecute(result: String) { - (targetFragment as KeystoreDialog).dismiss() - (targetFragment!!.targetFragment as NewWalletDialog1).dismiss() - daemonModel.commands.callAttr("select_wallet", result) - (activity as MainActivity).updateDrawer() + + val multisig = arguments!!.getBoolean("multisig") + + /** + * For multisig wallets, we need to first show the master key to the 1st cosigner, and + * then prompt for data for all other cosigners by calling the KeystoreDialog again. + */ + if (multisig) { + + val currentCosigner = arguments!!.getInt("i_signer") + val numCosigners = arguments!!.getInt("cosigners") + + if (currentCosigner < numCosigners) { + // The first cosigner sees their master public key; others are prompted for data + val nextDialog: DialogFragment = if (currentCosigner == 1) { + MasterPublicKeyDialog() + } else { + KeystoreDialog() + } + + arguments!!.putInt("i_signer", currentCosigner + 1) + + nextDialog.setArguments(arguments) + showDialog(this, nextDialog) + } else { // last cosigner done; finalize wallet + daemonModel.commands.callAttr("select_wallet", result) + (activity as MainActivity).updateDrawer() + } + } else { + // In a standard wallet, close the dialogs and open the newly created wallet. + (targetFragment as KeystoreDialog).dismiss() + (targetFragment!!.targetFragment as NewWalletDialog1).dismiss() + daemonModel.commands.callAttr("select_wallet", result) + (activity as MainActivity).updateDrawer() + } } } @@ -208,11 +312,22 @@ class NewWalletSeedDialog : NewWalletDialog2() { !libBitcoin.callAttr("is_bip32_derivation", derivation).toBoolean()) { throw ToastException(R.string.Derivation_invalid) } - daemonModel.commands.callAttr( + + val multisig = arguments!!.getBoolean("multisig") + val ks = daemonModel.commands.callAttr( "create", name, password, Kwarg("seed", input), Kwarg("passphrase", passphrase), + Kwarg("multisig", multisig), Kwarg("bip39_derivation", derivation)) + + if (multisig) { + keystores.add(ks) + + val masterKey = ks.callAttr("get_master_public_key").toString() + arguments!!.putString("masterKey", masterKey) + } + } catch (e: PyException) { if (e.message!!.startsWith("InvalidSeed")) { throw ToastException(R.string.the_seed_you_entered_does_not_appear) @@ -291,8 +406,23 @@ class NewWalletImportMasterDialog : NewWalletDialog2() { override fun onShowDialog() { super.onShowDialog() - tvPrompt.setText(getString(R.string.to_create_a_watching) + " " + - getString(R.string.to_create_a_spending)) + + val multisig = arguments!!.getBoolean("multisig") + val currentCosigner = arguments!!.getInt("i_signer") + + val keyPrompt = if (multisig && currentCosigner != 1) { + getString(R.string.please_enter_the_master_public_key_xpub) + " " + + getString(R.string.enter_their) + } else { + getString(R.string.to_create_a_watching) + " " + + getString(R.string.to_create_a_spending) + } + tvPrompt.setText(keyPrompt) + + if (multisig && currentCosigner != 1) { + dialog.setTitle(getString(R.string.Add_Cosigner) + " $currentCosigner") + } + dialog.getButton(AlertDialog.BUTTON_NEUTRAL).setOnClickListener { scanQR(this) } } @@ -309,7 +439,18 @@ class NewWalletImportMasterDialog : NewWalletDialog2() { override fun onCreateWallet(name: String, password: String) { val key = input.trim() if (libKeystore.callAttr("is_bip32_key", key).toBoolean()) { - daemonModel.commands.callAttr("create", name, password, Kwarg("master", key)) + val multisig = arguments!!.getBoolean("multisig") + val ks = daemonModel.commands.callAttr( + "create", name, password, + Kwarg("master", key), + Kwarg("multisig", multisig) + ) + + if (multisig) { + val masterKey = ks.callAttr("get_master_public_key").toString() + arguments!!.putString("masterKey", masterKey) + keystores.add(ks) + } } else { throw ToastException(R.string.please_specify) } @@ -351,7 +492,7 @@ fun setupSeedDialog(fragment: AlertDialogFragment) { // Choose the number of multi-sig wallet cosigners class CosignerDialog : AlertDialogFragment() { override fun onBuildDialog(builder: AlertDialog.Builder) { - builder.setTitle(R.string.New_wallet) + builder.setTitle(R.string.Multi_signature) .setView(R.layout.multisig_cosigners) .setPositiveButton(R.string.next, null) .setNegativeButton(R.string.cancel, null) @@ -362,8 +503,10 @@ class CosignerDialog : AlertDialogFragment() { msFromText.text = getString(R.string.from_cosigners).replace("%d", "2") msReqText.text = getString(R.string.require_signatures).replace("%d", "2") + var numCosigners = 2 + var numSignatures = 2 - // Handle number of cosigners + // Handle the total number of cosigners msFrom?.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener { override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) { msFromText.text = getString(R.string.from_cosigners).replace("%d", progress.toString()) @@ -372,19 +515,15 @@ class CosignerDialog : AlertDialogFragment() { if (progress == 0) { msFrom.progress = 1 } + numCosigners = progress } - override fun onStartTrackingTouch(seekBar: SeekBar?) { - - } - - override fun onStopTrackingTouch(seekBar: SeekBar) { - - } + override fun onStartTrackingTouch(seekBar: SeekBar?) {} + override fun onStopTrackingTouch(seekBar: SeekBar) {} }) - // Handle number of required signatures + // Handle the number of required signatures msReq?.setOnSeekBarChangeListener(object: SeekBar.OnSeekBarChangeListener { override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) { msReqText.text = getString(R.string.require_signatures).replace("%d", progress.toString()) @@ -392,19 +531,83 @@ class CosignerDialog : AlertDialogFragment() { if (progress == 0) { msReq.progress = 1 } + numSignatures = progress } - override fun onStartTrackingTouch(seekBar: SeekBar?) { + override fun onStartTrackingTouch(seekBar: SeekBar?) {} + override fun onStopTrackingTouch(seekBar: SeekBar?) {} + }) + + dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener { + try { + + val nextDialog: DialogFragment + nextDialog = KeystoreDialog() + + arguments!!.putBoolean("multisig", true) + arguments!!.putInt("i_signer", 1) // current co-signer; will update + arguments!!.putInt("cosigners", numCosigners) + arguments!!.putInt("signatures", numSignatures) + + nextDialog.setArguments(arguments) + showDialog(this, nextDialog) + } catch (e: ToastException) { + e.show() } + } + } +} + +/** + * View and copy the master public key of the (multisig) wallet. + */ +class MasterPublicKeyDialog : AlertDialogFragment() { + override fun onBuildDialog(builder: AlertDialog.Builder) { + builder.setTitle(R.string.master_public_key) + .setView(R.layout.show_master_key) + .setPositiveButton(R.string.next, null) + .setNegativeButton(R.string.cancel, null) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + fabCopyMasterKey.setOnClickListener { + copyTextToClipboard() + } + } + + override fun onShowDialog() { + super.onShowDialog() + + walletMasterKey.setText(arguments!!.getString("masterKey")) + // walletMasterKey.setInputType(InputType.TYPE_NULL) + walletMasterKey.setFocusable(false) - override fun onStopTrackingTouch(seekBar: SeekBar?) { + dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener { + try { + + val nextDialog: DialogFragment + nextDialog = KeystoreDialog() + + nextDialog.setArguments(arguments) + showDialog(this, nextDialog) + } catch (e: ToastException) { + e.show() } + } + } - }) + + private fun copyTextToClipboard() { + val textToCopy = walletMasterKey.text + val clipboardManager = activity!!.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager + val clipData = ClipData.newPlainText("text", textToCopy) + clipboardManager.setPrimaryClip(clipData) + Toast.makeText(this.context, "Master key copied to clipboard", Toast.LENGTH_LONG).show() } } - fun seedAdvice(seed: String): String { return app.getString(R.string.please_save, seed.split(" ").size) + " " + app.getString(R.string.this_seed_will) + " " + From 29da35d98ed61f1034d1cb48940372a89ce956d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aldin=20Kova=C4=8Devi=C4=87?= Date: Fri, 9 Apr 2021 23:28:17 +0200 Subject: [PATCH 042/208] Android: Added wallet type near the wallet name in the appbar. --- .../src/main/java/org/electroncash/electroncash3/Daemon.kt | 4 ++++ .../src/main/java/org/electroncash/electroncash3/Main.kt | 7 ++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/android/app/src/main/java/org/electroncash/electroncash3/Daemon.kt b/android/app/src/main/java/org/electroncash/electroncash3/Daemon.kt index 1b8b0b92c1a5..b711bd3ba3c1 100644 --- a/android/app/src/main/java/org/electroncash/electroncash3/Daemon.kt +++ b/android/app/src/main/java/org/electroncash/electroncash3/Daemon.kt @@ -34,6 +34,10 @@ class DaemonModel(val config: PyObject) { val wallet = this.wallet return if (wallet == null) null else wallet.callAttr("basename").toString() } + val walletType: String? + get() { + return if (wallet == null) null else commands.callAttr("get", "wallet_type").toString() + } lateinit var watchdog: Runnable diff --git a/android/app/src/main/java/org/electroncash/electroncash3/Main.kt b/android/app/src/main/java/org/electroncash/electroncash3/Main.kt index 028291576a90..6aabae72f67a 100644 --- a/android/app/src/main/java/org/electroncash/electroncash3/Main.kt +++ b/android/app/src/main/java/org/electroncash/electroncash3/Main.kt @@ -11,6 +11,7 @@ import android.os.Bundle import android.text.Html import android.text.SpannableStringBuilder import android.text.method.LinkMovementMethod +import android.util.Log import android.view.LayoutInflater import android.view.Menu import android.view.MenuItem @@ -146,7 +147,11 @@ class MainActivity : AppCompatActivity(R.layout.main) { } fun onCaption(caption: Caption) { - val walletName = caption.walletName ?: app.getString(R.string.No_wallet) + // Get the wallet name + type + val walletName = if (daemonModel.walletType != null) { + caption.walletName + " [${daemonModel.walletType}]" + } else caption.walletName ?: app.getString(R.string.No_wallet) + if (resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT) { setTitle(walletName) supportActionBar!!.setSubtitle(caption.subtitle) From 1e73a501917ad1cb3091564ed1ca35a2cf544c3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aldin=20Kova=C4=8Devi=C4=87?= Date: Sat, 10 Apr 2021 00:44:08 +0200 Subject: [PATCH 043/208] Android: Added layouts and menu dropdown for wallet information (containing the master public key). --- .../main/res/layout/wallet_information.xml | 90 +++++++++++++++++++ android/app/src/main/res/menu/wallet.xml | 3 + 2 files changed, 93 insertions(+) create mode 100644 android/app/src/main/res/layout/wallet_information.xml diff --git a/android/app/src/main/res/layout/wallet_information.xml b/android/app/src/main/res/layout/wallet_information.xml new file mode 100644 index 000000000000..6190d5bd2d88 --- /dev/null +++ b/android/app/src/main/res/layout/wallet_information.xml @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/app/src/main/res/menu/wallet.xml b/android/app/src/main/res/menu/wallet.xml index 1d4c2c975cba..b1a39e55f84c 100644 --- a/android/app/src/main/res/menu/wallet.xml +++ b/android/app/src/main/res/menu/wallet.xml @@ -18,6 +18,9 @@ android:id="@+id/menuShowSeed" android:title="@string/show_seed"/> + From fd4d34676a9b05aa3f85d0b590088f7fcd8798be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aldin=20Kova=C4=8Devi=C4=87?= Date: Sat, 10 Apr 2021 00:44:37 +0200 Subject: [PATCH 044/208] Android: Added logic to view wallet information. --- .../org/electroncash/electroncash3/Daemon.kt | 2 + .../org/electroncash/electroncash3/Main.kt | 45 +++++++++++++++++-- 2 files changed, 44 insertions(+), 3 deletions(-) diff --git a/android/app/src/main/java/org/electroncash/electroncash3/Daemon.kt b/android/app/src/main/java/org/electroncash/electroncash3/Daemon.kt index b711bd3ba3c1..cfe4ce8930c7 100644 --- a/android/app/src/main/java/org/electroncash/electroncash3/Daemon.kt +++ b/android/app/src/main/java/org/electroncash/electroncash3/Daemon.kt @@ -38,6 +38,8 @@ class DaemonModel(val config: PyObject) { get() { return if (wallet == null) null else commands.callAttr("get", "wallet_type").toString() } + val scriptType: String? + get() = daemonModel.wallet?.get("txin_type").toString() lateinit var watchdog: Runnable diff --git a/android/app/src/main/java/org/electroncash/electroncash3/Main.kt b/android/app/src/main/java/org/electroncash/electroncash3/Main.kt index 6aabae72f67a..511122055909 100644 --- a/android/app/src/main/java/org/electroncash/electroncash3/Main.kt +++ b/android/app/src/main/java/org/electroncash/electroncash3/Main.kt @@ -4,6 +4,9 @@ package org.electroncash.electroncash3 import android.annotation.SuppressLint import android.app.Activity +import android.content.ClipData +import android.content.ClipboardManager +import android.content.Context import android.content.Intent import android.content.res.Configuration import android.net.Uri @@ -28,7 +31,10 @@ import androidx.fragment.app.Fragment import androidx.lifecycle.observe import com.chaquo.python.Kwarg import kotlinx.android.synthetic.main.main.* +import kotlinx.android.synthetic.main.show_master_key.* +import kotlinx.android.synthetic.main.show_master_key.walletMasterKey import kotlinx.android.synthetic.main.wallet_export.* +import kotlinx.android.synthetic.main.wallet_information.* import kotlinx.android.synthetic.main.wallet_open.* import kotlinx.android.synthetic.main.wallet_rename.* import java.io.File @@ -236,6 +242,7 @@ class MainActivity : AppCompatActivity(R.layout.main) { } R.id.menuChangePassword -> showDialog(this, PasswordChangeDialog()) R.id.menuShowSeed -> { showDialog(this, SeedPasswordDialog()) } + R.id.menuMasterPublicKey -> { showDialog(this, WalletInformationDialog()) } R.id.menuExportSigned -> { try { showDialog(this, SendDialog().apply { @@ -659,15 +666,47 @@ class SeedPasswordDialog : PasswordDialog() { } } - class SeedDialog : AlertDialogFragment() { override fun onBuildDialog(builder: AlertDialog.Builder) { builder.setTitle(R.string.Wallet_seed) - .setView(R.layout.wallet_new_2) - .setPositiveButton(android.R.string.ok, null) + .setView(R.layout.wallet_new_2) + .setPositiveButton(android.R.string.ok, null) } override fun onShowDialog() { setupSeedDialog(this) } } + +class WalletInformationDialog : AlertDialogFragment() { + override fun onBuildDialog(builder: AlertDialog.Builder) { + + builder.setTitle(R.string.wallet_information) + .setView(R.layout.wallet_information) + .setPositiveButton(android.R.string.ok, null) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + fabCopyMasterKey2.setOnClickListener { + val textToCopy = walletMasterKey.text + val clipboardManager = activity!!.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager + val clipData = ClipData.newPlainText("text", textToCopy) + clipboardManager.setPrimaryClip(clipData) + Toast.makeText(this.context, "Master key copied to clipboard", Toast.LENGTH_LONG).show() + } + } + + override fun onShowDialog() { + super.onShowDialog() + + val masterKey = daemonModel.commands.callAttr("getmpk").toString() + walletMasterKey.setText(masterKey) + walletMasterKey.setFocusable(false) + + idWalletName.setText(getString(R.string.wallet_name) + ": ${daemonModel.walletName}") + idWalletType.setText(getString(R.string.wallet_type) + ": ${daemonModel.walletType}") + idScriptType.setText(getString(R.string.script_type) + ": ${daemonModel.scriptType}") + } +} From 9f82245825a2da8cba4d6fdb4e9588e96964601e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aldin=20Kova=C4=8Devi=C4=87?= Date: Sat, 10 Apr 2021 13:39:14 +0200 Subject: [PATCH 045/208] Android: improved the layout of the wallet information popup. --- .../org/electroncash/electroncash3/Main.kt | 8 +-- .../main/res/layout/wallet_information.xml | 64 +++++++++++++++---- 2 files changed, 53 insertions(+), 19 deletions(-) diff --git a/android/app/src/main/java/org/electroncash/electroncash3/Main.kt b/android/app/src/main/java/org/electroncash/electroncash3/Main.kt index 511122055909..b96d53c33410 100644 --- a/android/app/src/main/java/org/electroncash/electroncash3/Main.kt +++ b/android/app/src/main/java/org/electroncash/electroncash3/Main.kt @@ -14,7 +14,6 @@ import android.os.Bundle import android.text.Html import android.text.SpannableStringBuilder import android.text.method.LinkMovementMethod -import android.util.Log import android.view.LayoutInflater import android.view.Menu import android.view.MenuItem @@ -31,7 +30,6 @@ import androidx.fragment.app.Fragment import androidx.lifecycle.observe import com.chaquo.python.Kwarg import kotlinx.android.synthetic.main.main.* -import kotlinx.android.synthetic.main.show_master_key.* import kotlinx.android.synthetic.main.show_master_key.walletMasterKey import kotlinx.android.synthetic.main.wallet_export.* import kotlinx.android.synthetic.main.wallet_information.* @@ -705,8 +703,8 @@ class WalletInformationDialog : AlertDialogFragment() { walletMasterKey.setText(masterKey) walletMasterKey.setFocusable(false) - idWalletName.setText(getString(R.string.wallet_name) + ": ${daemonModel.walletName}") - idWalletType.setText(getString(R.string.wallet_type) + ": ${daemonModel.walletType}") - idScriptType.setText(getString(R.string.script_type) + ": ${daemonModel.scriptType}") + idWalletName.setText(daemonModel.walletName) + idWalletType.setText(daemonModel.walletType) + idScriptType.setText(daemonModel.scriptType) } } diff --git a/android/app/src/main/res/layout/wallet_information.xml b/android/app/src/main/res/layout/wallet_information.xml index 6190d5bd2d88..15206f09728f 100644 --- a/android/app/src/main/res/layout/wallet_information.xml +++ b/android/app/src/main/res/layout/wallet_information.xml @@ -11,41 +11,35 @@ android:layout_height="wrap_content"> + app:layout_constraintTop_toBottomOf="@+id/textView28" /> + app:layout_constraintTop_toBottomOf="@+id/textView29" /> + + + + + + + + + \ No newline at end of file From 3c0da3e7fb2f6bac675344c89335b3c0a4deac60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aldin=20Kova=C4=8Devi=C4=87?= Date: Sat, 10 Apr 2021 13:50:57 +0200 Subject: [PATCH 046/208] Android: refactored to use the already existing copyToClipoard() method; some updates to Daemon model. --- .../org/electroncash/electroncash3/Daemon.kt | 4 ++-- .../java/org/electroncash/electroncash3/Main.kt | 5 +---- .../org/electroncash/electroncash3/NewWallet.kt | 16 +--------------- 3 files changed, 4 insertions(+), 21 deletions(-) diff --git a/android/app/src/main/java/org/electroncash/electroncash3/Daemon.kt b/android/app/src/main/java/org/electroncash/electroncash3/Daemon.kt index cfe4ce8930c7..b508d9f91a4e 100644 --- a/android/app/src/main/java/org/electroncash/electroncash3/Daemon.kt +++ b/android/app/src/main/java/org/electroncash/electroncash3/Daemon.kt @@ -32,14 +32,14 @@ class DaemonModel(val config: PyObject) { val walletName: String? get() { val wallet = this.wallet - return if (wallet == null) null else wallet.callAttr("basename").toString() + return wallet?.callAttr("basename")?.toString() } val walletType: String? get() { return if (wallet == null) null else commands.callAttr("get", "wallet_type").toString() } val scriptType: String? - get() = daemonModel.wallet?.get("txin_type").toString() + get() = wallet?.get("txin_type").toString() lateinit var watchdog: Runnable diff --git a/android/app/src/main/java/org/electroncash/electroncash3/Main.kt b/android/app/src/main/java/org/electroncash/electroncash3/Main.kt index b96d53c33410..d12a2226f537 100644 --- a/android/app/src/main/java/org/electroncash/electroncash3/Main.kt +++ b/android/app/src/main/java/org/electroncash/electroncash3/Main.kt @@ -689,10 +689,7 @@ class WalletInformationDialog : AlertDialogFragment() { fabCopyMasterKey2.setOnClickListener { val textToCopy = walletMasterKey.text - val clipboardManager = activity!!.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager - val clipData = ClipData.newPlainText("text", textToCopy) - clipboardManager.setPrimaryClip(clipData) - Toast.makeText(this.context, "Master key copied to clipboard", Toast.LENGTH_LONG).show() + copyToClipboard(textToCopy, R.string.master_public_key) } } diff --git a/android/app/src/main/java/org/electroncash/electroncash3/NewWallet.kt b/android/app/src/main/java/org/electroncash/electroncash3/NewWallet.kt index c7bccdb932e4..0519e1430d45 100644 --- a/android/app/src/main/java/org/electroncash/electroncash3/NewWallet.kt +++ b/android/app/src/main/java/org/electroncash/electroncash3/NewWallet.kt @@ -179,10 +179,6 @@ class KeystoreDialog : AlertDialogFragment() { showDialog(this, nextDialog) } catch (e: ToastException) { e.show() } } - -// dialog.getButton(AlertDialog.BUTTON_NEGATIVE).setOnClickListener { -// arguments = resetCosigner(arguments) -// } } } @@ -573,7 +569,7 @@ class MasterPublicKeyDialog : AlertDialogFragment() { super.onViewCreated(view, savedInstanceState) fabCopyMasterKey.setOnClickListener { - copyTextToClipboard() + copyToClipboard(walletMasterKey.text, R.string.master_public_key) } } @@ -581,7 +577,6 @@ class MasterPublicKeyDialog : AlertDialogFragment() { super.onShowDialog() walletMasterKey.setText(arguments!!.getString("masterKey")) - // walletMasterKey.setInputType(InputType.TYPE_NULL) walletMasterKey.setFocusable(false) dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener { @@ -597,15 +592,6 @@ class MasterPublicKeyDialog : AlertDialogFragment() { } } } - - - private fun copyTextToClipboard() { - val textToCopy = walletMasterKey.text - val clipboardManager = activity!!.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager - val clipData = ClipData.newPlainText("text", textToCopy) - clipboardManager.setPrimaryClip(clipData) - Toast.makeText(this.context, "Master key copied to clipboard", Toast.LENGTH_LONG).show() - } } fun seedAdvice(seed: String): String { From 7072f27f2a35f6d1a84c4de80db4ace052dac4ee Mon Sep 17 00:00:00 2001 From: SomberNight Date: Mon, 12 Apr 2021 18:21:57 +0200 Subject: [PATCH 047/208] appimage build: bundle dependencies of Qt xcb plugin We recently bumped the bundled version of PyQt to 5.15.5. It seems the new version has a lot more dynamic dependencies. From https://doc.qt.io/qt-5/linux-requirements.html : > From Qt 5.15 onwards, Qt does require libxcb 1.11. Also, the -qt-xcb > configure option got removed that was bundling some of the libs below. We are using the prebuilt wheels for PyQt5 from PyPI. Presumably those had previously been built using the -qt-xcb option. --- contrib/build-linux/appimage/Dockerfile_ub1804 | 13 +++++++++++++ contrib/build-linux/appimage/_build.sh | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/contrib/build-linux/appimage/Dockerfile_ub1804 b/contrib/build-linux/appimage/Dockerfile_ub1804 index 953fbb35d856..e2cadfd71770 100644 --- a/contrib/build-linux/appimage/Dockerfile_ub1804 +++ b/contrib/build-linux/appimage/Dockerfile_ub1804 @@ -28,8 +28,21 @@ RUN echo deb ${UBUNTU_MIRROR} bionic main restricted universe multiverse > /etc/ libpcsclite-dev=1.8.23-1 \ swig=3.0.12-1 \ libxkbcommon-x11-0=0.8.2-1~ubuntu18.04.1 \ + libxcb1=1.13-2~ubuntu18.04 \ + libxcb-icccm4=0.4.1-1ubuntu1 \ + libxcb-image0=0.4.0-1build1 \ + libxcb-keysyms1=0.4.0-1 \ + libxcb-randr0=1.13-2~ubuntu18.04 \ + libxcb-render-util0=0.3.9-1 \ + libxcb-render0=1.13-2~ubuntu18.04 \ + libxcb-shape0=1.13-2~ubuntu18.04 \ + libxcb-shm0=1.13-2~ubuntu18.04 \ + libxcb-sync1=1.13-2~ubuntu18.04 \ libxcb-util1=0.4.0-0ubuntu3 \ + libxcb-xfixes0=1.13-2~ubuntu18.04 \ libxcb-xinerama0=1.13-2~ubuntu18.04 \ + libxcb-xkb1=1.13-2~ubuntu18.04 \ + libx11-xcb1=2:1.6.4-3ubuntu0.3 \ autopoint=0.19.8.1-6ubuntu0.3 \ zlib1g-dev=1:1.2.11.dfsg-0ubuntu2 \ libfreetype6=2.8.1-2ubuntu2.1 \ diff --git a/contrib/build-linux/appimage/_build.sh b/contrib/build-linux/appimage/_build.sh index f5a0b1b46c5f..40b6711a2601 100755 --- a/contrib/build-linux/appimage/_build.sh +++ b/contrib/build-linux/appimage/_build.sh @@ -154,7 +154,7 @@ cp -fp /usr/lib/x86_64-linux-gnu/libusb-1.0.so "$APPDIR"/usr/lib/x86_64-linux-gn cp -f /usr/lib/x86_64-linux-gnu/libxkbcommon-x11.so.0 "$APPDIR"/usr/lib/x86_64-linux-gnu || fail "Could not copy libxkbcommon-x11" # some distros lack some libxcb libraries (see #2189, #2196) -cp -f /usr/lib/x86_64-linux-gnu/libxcb-* "$APPDIR"/usr/lib/x86_64-linux-gnu || fail "Could not copy libxkcb" +cp -f /usr/lib/x86_64-linux-gnu/libxcb* "$APPDIR"/usr/lib/x86_64-linux-gnu || fail "Could not copy libxkcb" info "Stripping binaries of debug symbols" # "-R .note.gnu.build-id" also strips the build id From c25a0e48a21b91568c28914318d4b38ed8a8a4ad Mon Sep 17 00:00:00 2001 From: Calin Culianu Date: Fri, 16 Apr 2021 07:39:16 -0600 Subject: [PATCH 048/208] Remove CPFP (child pays for parent) This feature is going away from BCH very soon with the May 15th 2021 network upgrade. As such, we remove it now. It hardly was used anyway on BCH so nobody will miss it if it's simply gone right away without a transition period. Miners running BCHN 23.0.0 don't have CPFP code anymore anyway when generating blocks so.. it's a good idea to remove this ASAP as well. --- electroncash/wallet.py | 21 ----------- electroncash_gui/qt/history_list.py | 4 --- electroncash_gui/qt/main_window.py | 56 ----------------------------- 3 files changed, 81 deletions(-) diff --git a/electroncash/wallet.py b/electroncash/wallet.py index d6bbe1e75283..583f808f6e16 100644 --- a/electroncash/wallet.py +++ b/electroncash/wallet.py @@ -2221,27 +2221,6 @@ def address_is_old(self, address, age_limit=2): break # ok, it's old. not need to keep looping return age > age_limit - def cpfp(self, tx, fee, sign_schnorr=None): - ''' sign_schnorr is a bool or None for auto ''' - sign_schnorr = self.is_schnorr_enabled() if sign_schnorr is None else bool(sign_schnorr) - txid = tx.txid() - for i, o in enumerate(tx.outputs()): - otype, address, value = o - if otype == TYPE_ADDRESS and self.is_mine(address): - break - else: - return - coins = self.get_addr_utxo(address) - item = coins.get(txid+':%d'%i) - if not item: - return - self.add_input_info(item) - inputs = [item] - outputs = [(TYPE_ADDRESS, address, value - fee)] - locktime = self.get_local_height() - # note: no need to call tx.BIP_LI01_sort() here - single input/output - return Transaction.from_io(inputs, outputs, locktime=locktime, sign_schnorr=sign_schnorr) - def add_input_info(self, txin): address = txin['address'] if self.is_mine(address): diff --git a/electroncash_gui/qt/history_list.py b/electroncash_gui/qt/history_list.py index e66207adb400..b9e93fc711b6 100644 --- a/electroncash_gui/qt/history_list.py +++ b/electroncash_gui/qt/history_list.py @@ -263,10 +263,6 @@ def create_menu(self, position): lambda: self.currentItem() and self.editItem(self.currentItem(), column)) label = self.wallet.get_label(tx_hash) or None menu.addAction(_("&Details"), lambda: self.parent.show_transaction(tx, label)) - if is_unconfirmed and tx: - child_tx = self.wallet.cpfp(tx, 0) - if child_tx: - menu.addAction(_("Child pays for parent"), lambda: self.parent.cpfp(tx, child_tx)) if pr_key: menu.addAction(self.invoiceIcon, _("View invoice"), lambda: self.parent.show_invoice(pr_key)) if tx_URL: diff --git a/electroncash_gui/qt/main_window.py b/electroncash_gui/qt/main_window.py index 72e7d67c5059..1585c4820da8 100644 --- a/electroncash_gui/qt/main_window.py +++ b/electroncash_gui/qt/main_window.py @@ -4913,62 +4913,6 @@ def hardware_wallet_support(self): d.exec_() self.hardwarewalletdialog = None # allow python to GC - def cpfp(self, parent_tx, new_tx): - total_size = parent_tx.estimated_size() + new_tx.estimated_size() - d = WindowModalDialog(self.top_level_window(), _('Child Pays for Parent')) - vbox = QVBoxLayout(d) - msg = ( - "A CPFP is a transaction that sends an unconfirmed output back to " - "yourself, with a high fee. The goal is to have miners confirm " - "the parent transaction in order to get the fee attached to the " - "child transaction.") - vbox.addWidget(WWLabel(_(msg))) - msg2 = ("The proposed fee is computed using your " - "fee/kB settings, applied to the total size of both child and " - "parent transactions. After you broadcast a CPFP transaction, " - "it is normal to see a new unconfirmed transaction in your history.") - vbox.addWidget(WWLabel(_(msg2))) - grid = QGridLayout() - grid.addWidget(QLabel(_('Total size') + ':'), 0, 0) - grid.addWidget(QLabel(_('{total_size} bytes').format(total_size=total_size)), 0, 1) - max_fee = new_tx.output_value() - grid.addWidget(QLabel(_('Input amount') + ':'), 1, 0) - grid.addWidget(QLabel(self.format_amount(max_fee) + ' ' + self.base_unit()), 1, 1) - output_amount = QLabel('') - grid.addWidget(QLabel(_('Output amount') + ':'), 2, 0) - grid.addWidget(output_amount, 2, 1) - fee_e = BTCAmountEdit(self.get_decimal_point) - def f(x): - a = max_fee - fee_e.get_amount() - output_amount.setText((self.format_amount(a) + ' ' + self.base_unit()) if a else '') - fee_e.textChanged.connect(f) - fee = self.config.fee_per_kb() * total_size / 1000 - fee_e.setAmount(fee) - grid.addWidget(QLabel(_('Fee' + ':')), 3, 0) - grid.addWidget(fee_e, 3, 1) - def on_rate(dyn, pos, fee_rate): - fee = fee_rate * total_size / 1000 - fee = min(max_fee, fee) - fee_e.setAmount(fee) - fee_slider = FeeSlider(self, self.config, on_rate) - fee_slider.update() - grid.addWidget(fee_slider, 4, 1) - vbox.addLayout(grid) - vbox.addLayout(Buttons(CancelButton(d), OkButton(d))) - result = d.exec_() - d.setParent(None) # So Python can GC - if not result: - return - fee = fee_e.get_amount() - if fee > max_fee: - self.show_error(_('Max fee exceeded')) - return - new_tx = self.wallet.cpfp(parent_tx, fee) - if new_tx is None: - self.show_error(_('CPFP no longer valid')) - return - self.show_transaction(new_tx) - def rebuild_history(self): if self.gui_object.warn_if_no_network(self): # Don't allow if offline mode. From 57eb2aadd368ddcdd2800e8d51eef2c5ddb5f1a3 Mon Sep 17 00:00:00 2001 From: scinklja <73796084+scinklja@users.noreply.github.com> Date: Sun, 18 Apr 2021 21:08:56 +0200 Subject: [PATCH 049/208] history list - fiat withdrawal in red --- electroncash_gui/qt/history_list.py | 1 + 1 file changed, 1 insertion(+) diff --git a/electroncash_gui/qt/history_list.py b/electroncash_gui/qt/history_list.py index b9e93fc711b6..e8a2cd8fa092 100644 --- a/electroncash_gui/qt/history_list.py +++ b/electroncash_gui/qt/history_list.py @@ -161,6 +161,7 @@ def on_update(self): if value and value < 0: item.setForeground(3, self.withdrawalBrush) item.setForeground(4, self.withdrawalBrush) + item.setForeground(6, self.withdrawalBrush) item.setData(0, Qt.UserRole, tx_hash) self.addTopLevelItem(item, tx_hash) if current_tx == tx_hash: From c7186df266c7bd30cd0a7b8d951303aba9631447 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aldin=20Kova=C4=8Devi=C4=87?= Date: Mon, 19 Apr 2021 14:50:20 +0200 Subject: [PATCH 050/208] Android: Updated the cosigner and signature sliders in the cosigner dialog to match the approach used in the Send dialog. --- .../electroncash/electroncash3/NewWallet.kt | 70 ++++++++++--------- .../main/res/layout/multisig_cosigners.xml | 16 ++--- 2 files changed, 44 insertions(+), 42 deletions(-) diff --git a/android/app/src/main/java/org/electroncash/electroncash3/NewWallet.kt b/android/app/src/main/java/org/electroncash/electroncash3/NewWallet.kt index 0519e1430d45..02222209a146 100644 --- a/android/app/src/main/java/org/electroncash/electroncash3/NewWallet.kt +++ b/android/app/src/main/java/org/electroncash/electroncash3/NewWallet.kt @@ -3,12 +3,8 @@ package org.electroncash.electroncash3 import android.app.Dialog import android.content.* import android.os.Bundle -import android.text.InputType import android.text.Selection -import android.util.Log -import android.view.LayoutInflater import android.view.View -import android.view.ViewGroup import android.widget.SeekBar import android.widget.Toast import androidx.appcompat.app.AlertDialog @@ -23,14 +19,17 @@ import kotlinx.android.synthetic.main.multisig_cosigners.* import kotlinx.android.synthetic.main.show_master_key.* import kotlinx.android.synthetic.main.wallet_new.* import kotlinx.android.synthetic.main.wallet_new_2.* -import java.security.KeyStore import kotlin.properties.Delegates.notNull val libKeystore by lazy { libMod("keystore") } val libWallet by lazy { libMod("wallet") } -val keystores: ArrayList = ArrayList() +val keystores by lazy { ArrayList() } + +val MAX_COSIGNERS = 15 +val COSIGNER_OFFSET = 2 // min. number of multisig cosigners = 2 +val SIGNATURE_OFFSET = 1 // min. number of req. multisig signatures = 1 class NewWalletDialog1 : AlertDialogFragment() { override fun onBuildDialog(builder: AlertDialog.Builder) { @@ -494,45 +493,48 @@ class CosignerDialog : AlertDialogFragment() { .setNegativeButton(R.string.cancel, null) } + val numCosigners: Int + get() = sbCosigners.progress + COSIGNER_OFFSET + + val numSignatures: Int + get() = sbSignatures.progress + SIGNATURE_OFFSET + override fun onShowDialog() { super.onShowDialog() - msFromText.text = getString(R.string.from_cosigners).replace("%d", "2") - msReqText.text = getString(R.string.require_signatures).replace("%d", "2") - var numCosigners = 2 - var numSignatures = 2 + tvCosigners.text = getString(R.string.from_cosigners, 2) + tvSignatures.text = getString(R.string.require_signatures, 2) // Handle the total number of cosigners - msFrom?.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener { - override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) { - msFromText.text = getString(R.string.from_cosigners).replace("%d", progress.toString()) - msReq.max = progress - - if (progress == 0) { - msFrom.progress = 1 + with (sbCosigners) { + progress = 0 + max = MAX_COSIGNERS - COSIGNER_OFFSET + + setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener { + override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) { + tvCosigners.text = getString(R.string.from_cosigners, numCosigners) + sbSignatures.max = numCosigners - 1 } - numCosigners = progress - } - - override fun onStartTrackingTouch(seekBar: SeekBar?) {} - override fun onStopTrackingTouch(seekBar: SeekBar) {} - }) + override fun onStartTrackingTouch(seekBar: SeekBar?) {} + override fun onStopTrackingTouch(seekBar: SeekBar) {} + }) + } // Handle the number of required signatures - msReq?.setOnSeekBarChangeListener(object: SeekBar.OnSeekBarChangeListener { - override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) { - msReqText.text = getString(R.string.require_signatures).replace("%d", progress.toString()) + with (sbSignatures) { + progress = numCosigners + max = SIGNATURE_OFFSET - if (progress == 0) { - msReq.progress = 1 + setOnSeekBarChangeListener(object: SeekBar.OnSeekBarChangeListener { + override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) { + tvSignatures.text = getString(R.string.require_signatures, numSignatures) } - numSignatures = progress - } - override fun onStartTrackingTouch(seekBar: SeekBar?) {} - override fun onStopTrackingTouch(seekBar: SeekBar?) {} - }) + override fun onStartTrackingTouch(seekBar: SeekBar?) {} + override fun onStopTrackingTouch(seekBar: SeekBar?) {} + }) + } dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener { try { @@ -562,7 +564,7 @@ class MasterPublicKeyDialog : AlertDialogFragment() { builder.setTitle(R.string.master_public_key) .setView(R.layout.show_master_key) .setPositiveButton(R.string.next, null) - .setNegativeButton(R.string.cancel, null) + .setNegativeButton(R.string.back, null) } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { diff --git a/android/app/src/main/res/layout/multisig_cosigners.xml b/android/app/src/main/res/layout/multisig_cosigners.xml index 81205b8f920c..b21573e75aaa 100644 --- a/android/app/src/main/res/layout/multisig_cosigners.xml +++ b/android/app/src/main/res/layout/multisig_cosigners.xml @@ -23,7 +23,7 @@ app:layout_constraintTop_toTopOf="parent" /> + app:layout_constraintStart_toStartOf="@+id/tvCosigners" + app:layout_constraintTop_toBottomOf="@+id/tvCosigners" /> + app:layout_constraintTop_toBottomOf="@+id/sbCosigners" /> + app:layout_constraintTop_toBottomOf="@+id/tvSignatures" /> \ No newline at end of file From 6b1875bbc2b36884def5aa44b5c9aaec8a62d4c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aldin=20Kova=C4=8Devi=C4=87?= Date: Mon, 19 Apr 2021 23:20:23 +0200 Subject: [PATCH 051/208] Android: Using Schnorr signatures for standard wallets; ECDSA for multsig wallets. --- .../app/src/main/java/org/electroncash/electroncash3/Send.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/android/app/src/main/java/org/electroncash/electroncash3/Send.kt b/android/app/src/main/java/org/electroncash/electroncash3/Send.kt index 616ed14e78fc..f8be7d4132bb 100644 --- a/android/app/src/main/java/org/electroncash/electroncash3/Send.kt +++ b/android/app/src/main/java/org/electroncash/electroncash3/Send.kt @@ -3,6 +3,7 @@ package org.electroncash.electroncash3 import android.content.Intent import android.os.Bundle import android.text.Editable +import android.util.Log import android.view.Menu import android.view.MenuItem import android.widget.SeekBar @@ -199,8 +200,9 @@ class SendDialog : TaskLauncherDialog() { val inputs = wallet.callAttr("get_spendable_coins", null, daemonModel.config, Kwarg("isInvoice", pr != null)) return try { + val signSchnorr = daemonModel.walletType == "standard" // sign with Schnorr in standard wallets TxResult(wallet.callAttr("make_unsigned_transaction", inputs, outputs, - daemonModel.config, Kwarg("sign_schnorr", true)), + daemonModel.config, Kwarg("sign_schnorr", signSchnorr)), isDummy) } catch (e: PyException) { TxResult(if (e.message!!.startsWith("NotEnoughFunds")) From 9ddb6ad1ea828abbb7ff5921cd79b31e4055272f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aldin=20Kova=C4=8Devi=C4=87?= Date: Mon, 19 Apr 2021 23:21:21 +0200 Subject: [PATCH 052/208] Android: on multisig wallets, the 'Send' dialog will correspond to the 'Sign' dialog. --- .../main/java/org/electroncash/electroncash3/Transactions.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/android/app/src/main/java/org/electroncash/electroncash3/Transactions.kt b/android/app/src/main/java/org/electroncash/electroncash3/Transactions.kt index eb1dd87ab717..6f544599a116 100644 --- a/android/app/src/main/java/org/electroncash/electroncash3/Transactions.kt +++ b/android/app/src/main/java/org/electroncash/electroncash3/Transactions.kt @@ -26,7 +26,10 @@ class TransactionsFragment : ListFragment(R.layout.transactions, R.id.rvTransact super.onViewCreated(view, savedInstanceState) btnSend.setOnClickListener { try { - showDialog(this, SendDialog()) + showDialog(this, SendDialog().apply { + arguments = Bundle().apply { + putBoolean("unbroadcasted", daemonModel.walletType != "standard") + }}) } catch (e: ToastException) { e.show() } } btnRequest.setOnClickListener { showDialog(this, NewRequestDialog()) } From 130877f3ed7a9928c45f36f0c86079be467f0b42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aldin=20Kova=C4=8Devi=C4=87?= Date: Mon, 19 Apr 2021 23:22:23 +0200 Subject: [PATCH 053/208] Android: Added Base43 encode/decode for working with Electron transaction QRs. --- .../org/electroncash/electroncash3/Base43.kt | 113 ++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 android/app/src/main/java/org/electroncash/electroncash3/Base43.kt diff --git a/android/app/src/main/java/org/electroncash/electroncash3/Base43.kt b/android/app/src/main/java/org/electroncash/electroncash3/Base43.kt new file mode 100644 index 000000000000..ffb4df0d700d --- /dev/null +++ b/android/app/src/main/java/org/electroncash/electroncash3/Base43.kt @@ -0,0 +1,113 @@ +package org.electroncash.electroncash3 + +import kotlin.text.Charsets.US_ASCII + +/** + * Base43 conversion, to use with transaction QRs generated by Electron Cash. + * Adjusted from the Java implementation by Andreas Schildbach at + * https://www.javatips.net/api/peercoin-android-wallet-master/wallet/src/com/matthewmitchell/peercoin_android_wallet/util/Base43.java + */ +object Base43 { + + val alphabet: CharArray = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ$*+-./:".toCharArray() + val indexes: IntArray = IntArray(128) + fun ByteArray.toHexString() = joinToString("") { "%02x".format(it) } + + init { + for (i in indexes.indices) { + indexes[i] = -1 + } + for (i in alphabet.indices) { + indexes[alphabet[i].toInt()] = i + } + } + + fun encode(input: ByteArray): String { + var input = input + if (input.isEmpty()) return "" + input = copyOfRange(input, 0, input.size) + + // Count leading zeroes. + var zeroCount = 0 + while (zeroCount < input.size && input[zeroCount].toInt() == 0) ++zeroCount + + // The actual encoding. + val temp = ByteArray(input.size * 2) + var j = temp.size + var startAt = zeroCount + while (startAt < input.size) { + val mod: Byte = divmod43(input, startAt) + if (input[startAt].toInt() == 0) ++startAt + temp[--j] = alphabet[mod.toInt()].toByte() + } + + // Strip extra '1' if there are some after decoding. + while (j < temp.size && temp[j].toChar() == alphabet[0]) ++j + + // Add as many leading '1' as there were leading zeros. + while (--zeroCount >= 0) temp[--j] = alphabet[0].toByte() + val output: ByteArray = copyOfRange(temp, j, temp.size) + return String(output, US_ASCII) + } + + @Throws(IllegalArgumentException::class) + fun decode(input: String): String { + if (input.isEmpty()) return "" + val input43 = ByteArray(input.length) + // Transform the String to a base43 byte sequence + for (i in input.indices) { + val c = input[i] + var digit43 = -1 + if (c.toInt() in 0..127) digit43 = indexes[c.toInt()] + require(digit43 >= 0) { "Illegal character $c at $i" } + input43[i] = digit43.toByte() + } + + // Count leading zeroes + var zeroCount = 0 + while (zeroCount < input43.size && input43[zeroCount].toInt() == 0) ++zeroCount + + // The encoding + val temp = ByteArray(input.length) + var j = temp.size + var startAt = zeroCount + while (startAt < input43.size) { + val mod: Byte = divmod256(input43, startAt) + if (input43[startAt].toInt() == 0) ++startAt + temp[--j] = mod + } + + // Do no add extra leading zeroes, move j to first non null byte. + while (j < temp.size && temp[j].toInt() == 0) ++j + + return copyOfRange(temp, j - zeroCount, temp.size).toHexString() + } + + private fun divmod43(number: ByteArray, startAt: Int): Byte { + var remainder = 0 + for (i in startAt until number.size) { + val digit256 = number[i].toInt() and 0xFF + val temp = remainder * 256 + digit256 + number[i] = (temp / 43).toByte() + remainder = temp % 43 + } + return remainder.toByte() + } + + private fun divmod256(number43: ByteArray, startAt: Int): Byte { + var remainder = 0 + for (i in startAt until number43.size) { + val digit58 = number43[i].toInt() and 0xFF + val temp = remainder * 43 + digit58 + number43[i] = (temp / 256).toByte() + remainder = temp % 256 + } + return remainder.toByte() + } + + private fun copyOfRange(source: ByteArray, from: Int, to: Int): ByteArray { + val range = ByteArray(to - from) + System.arraycopy(source, from, range, 0, range.size) + return range + } +} \ No newline at end of file From d0c4e263ed5734d37835b72839cd19adb2a53e7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aldin=20Kova=C4=8Devi=C4=87?= Date: Mon, 19 Apr 2021 23:23:27 +0200 Subject: [PATCH 054/208] Android: added the ability to load, sign and eventually broadcast hex transactions for multisig wallets. --- .../electroncash/electroncash3/ColdLoad.kt | 121 +++++++++++++++++- android/app/src/main/res/layout/load.xml | 41 +++++- 2 files changed, 156 insertions(+), 6 deletions(-) diff --git a/android/app/src/main/java/org/electroncash/electroncash3/ColdLoad.kt b/android/app/src/main/java/org/electroncash/electroncash3/ColdLoad.kt index 6151c5673f5f..249867a08233 100644 --- a/android/app/src/main/java/org/electroncash/electroncash3/ColdLoad.kt +++ b/android/app/src/main/java/org/electroncash/electroncash3/ColdLoad.kt @@ -2,10 +2,17 @@ package org.electroncash.electroncash3 import android.content.ClipboardManager import android.content.Intent +import android.os.Bundle +import android.view.View import android.widget.Toast import androidx.appcompat.app.AlertDialog +import com.chaquo.python.PyException +import com.chaquo.python.PyObject import com.google.zxing.integration.android.IntentIntegrator import kotlinx.android.synthetic.main.load.* +import kotlinx.android.synthetic.main.send.* +import java.lang.Exception +import java.lang.IllegalArgumentException val libTransaction by lazy { libMod("transaction") } @@ -17,12 +24,15 @@ val libTransaction by lazy { libMod("transaction") } // Valid transaction quickly show up in transactions. class ColdLoadDialog : AlertDialogFragment() { + /* 1 = signed, 0 = partially signed, -1 = invalid */ + var txStatus: Int? = null + override fun onBuildDialog(builder: AlertDialog.Builder) { builder.setTitle(R.string.load_transaction) .setView(R.layout.load) .setNegativeButton(android.R.string.cancel, null) .setNeutralButton(R.string.qr_code, null) - .setPositiveButton(R.string.send, null) + .setPositiveButton(R.string.OK, null) } override fun onShowDialog() { @@ -45,13 +55,39 @@ class ColdLoadDialog : AlertDialogFragment() { val currenttext = etTransaction.text //checks if text is blank. further validations can be added here dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = currenttext.isNotBlank() + + val tx = libTransaction.callAttr("Transaction", etTransaction.text.toString()) + getTxStatus(tx) + + // Check hex transaction signing status + when (txStatus) { + 1 -> { + dialog.getButton(AlertDialog.BUTTON_POSITIVE).setText(R.string.send) + dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = true + } + 0 -> { + dialog.getButton(AlertDialog.BUTTON_POSITIVE).setText(R.string.sign) + dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = true + } + else -> { + dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = false + } + } } // Receives the result of a QR scan. override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { val result = IntentIntegrator.parseActivityResult(requestCode, resultCode, data) + + // Try to decode the QR content as Base43; if that fails, treat it as is + val txHex: String = try { + Base43.decode(result.contents) + } catch (e: IllegalArgumentException) { + result.contents + } + if (result != null && result.contents != null) { - etTransaction.setText(result.contents) + etTransaction.setText(txHex) } else { super.onActivityResult(requestCode, resultCode, data) } @@ -59,6 +95,36 @@ class ColdLoadDialog : AlertDialogFragment() { fun onOK() { val tx = libTransaction.callAttr("Transaction", etTransaction.text.toString()) + + // Try to get the signing status of the transaction + // (partially signed, signed or invalid) + try { + if (txStatus == 1) { + broadcastSignedTransaction(tx) + } else { + signLoadedTransaction() + } + } catch (e: ToastException) { + e.show() + return + } + } + + /** + * Sign a loaded transaction. + */ + private fun signLoadedTransaction() { + val dialog = SignPasswordDialog() + dialog.setArguments(Bundle().apply { + putString("tx", etTransaction.text.toString()) + }) + showDialog(this, dialog) + } + + /** + * Broadcast a signed transaction. + */ + private fun broadcastSignedTransaction(tx: PyObject) { try { if (!daemonModel.isConnected()) { throw ToastException(R.string.not_connected) @@ -69,4 +135,55 @@ class ColdLoadDialog : AlertDialogFragment() { dismiss() } catch (e: ToastException) { e.show() } } + + + /** + * Check if a loaded transaction is signed. + * Displays the signing status below the raw TX field. + */ + fun getTxStatus(tx: PyObject) { + try { + val sigs = tx.callAttr("signature_count").toJava(IntArray::class.java) + + idStatusLabel.visibility = View.VISIBLE + idTxStatus.visibility = View.VISIBLE + + txStatus = if (sigs[0] == sigs[1]) { + idTxStatus.setText(R.string.signed) + 1 + } else { + idTxStatus.setText(getString(R.string.partially_signed) + " (${sigs[0]}/${sigs[1]})") + 0 + } + } catch (e: PyException) { + idStatusLabel.visibility = View.INVISIBLE + idTxStatus.visibility = View.INVISIBLE + txStatus = -1 + } + } +} + +/** + * Sign a loaded transaction dialog. + */ +class SignPasswordDialog : PasswordDialog() { + + val coldLoadDialog by lazy { targetFragment as ColdLoadDialog } + val tx by lazy { libTransaction.callAttr("Transaction", arguments!!.getString("tx")) } + val wallet = daemonModel.wallet!! + + override fun onPassword(password: String) { + wallet.callAttr("sign_transaction", tx, password) + + postToUiThread { + coldLoadDialog.etTransaction.setText(tx.toString()) + } + } + + override fun onPostExecute(result: Unit) { + if (coldLoadDialog.txStatus != 1) { + coldLoadDialog.dismiss() + copyToClipboard(tx.toString(), R.string.signed_transaction) + } + } } \ No newline at end of file diff --git a/android/app/src/main/res/layout/load.xml b/android/app/src/main/res/layout/load.xml index 6a4926b0c724..9ff31aea8712 100644 --- a/android/app/src/main/res/layout/load.xml +++ b/android/app/src/main/res/layout/load.xml @@ -24,32 +24,65 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent"/> + + - + + - + app:layout_constraintVertical_bias="0.0" + app:srcCompat="@drawable/ic_paste_24dp" /> + + + + + From 308e1a09f9c90c9c9bcf85304616197d3ec67c97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aldin=20Kova=C4=8Devi=C4=87?= Date: Tue, 20 Apr 2021 12:23:28 +0200 Subject: [PATCH 055/208] Android: Update 'Load Transaction' to prevent signing the transaction again from the same wallet. --- .../electroncash/electroncash3/ColdLoad.kt | 60 ++++++++++++------- 1 file changed, 38 insertions(+), 22 deletions(-) diff --git a/android/app/src/main/java/org/electroncash/electroncash3/ColdLoad.kt b/android/app/src/main/java/org/electroncash/electroncash3/ColdLoad.kt index 249867a08233..8df174b8561e 100644 --- a/android/app/src/main/java/org/electroncash/electroncash3/ColdLoad.kt +++ b/android/app/src/main/java/org/electroncash/electroncash3/ColdLoad.kt @@ -10,8 +10,6 @@ import com.chaquo.python.PyException import com.chaquo.python.PyObject import com.google.zxing.integration.android.IntentIntegrator import kotlinx.android.synthetic.main.load.* -import kotlinx.android.synthetic.main.send.* -import java.lang.Exception import java.lang.IllegalArgumentException @@ -56,20 +54,20 @@ class ColdLoadDialog : AlertDialogFragment() { //checks if text is blank. further validations can be added here dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = currenttext.isNotBlank() - val tx = libTransaction.callAttr("Transaction", etTransaction.text.toString()) - getTxStatus(tx) + val tx: PyObject + if (currenttext.isNotBlank()) { + tx = libTransaction.callAttr("Transaction", etTransaction.text.toString()) - // Check hex transaction signing status - when (txStatus) { - 1 -> { - dialog.getButton(AlertDialog.BUTTON_POSITIVE).setText(R.string.send) - dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = true - } - 0 -> { + updateStatusText(tx) + + // Check hex transaction signing status + if (canSign(tx)) { dialog.getButton(AlertDialog.BUTTON_POSITIVE).setText(R.string.sign) dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = true - } - else -> { + } else if (canBroadcast(tx)) { + dialog.getButton(AlertDialog.BUTTON_POSITIVE).setText(R.string.send) + dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = true + } else { dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = false } } @@ -96,10 +94,11 @@ class ColdLoadDialog : AlertDialogFragment() { fun onOK() { val tx = libTransaction.callAttr("Transaction", etTransaction.text.toString()) - // Try to get the signing status of the transaction - // (partially signed, signed or invalid) + // If transaction can be broadcasted, broadcast it. + // Otherwise, prompt for signing. If the transaction hex is invalid, + // the OK button will be disabled, regardless. try { - if (txStatus == 1) { + if (canBroadcast(tx)) { broadcastSignedTransaction(tx) } else { signLoadedTransaction() @@ -140,29 +139,46 @@ class ColdLoadDialog : AlertDialogFragment() { /** * Check if a loaded transaction is signed. * Displays the signing status below the raw TX field. + * (signed, partially signed, or invisible label, if the signatures are invalid) */ - fun getTxStatus(tx: PyObject) { + private fun updateStatusText(tx: PyObject) { try { val sigs = tx.callAttr("signature_count").toJava(IntArray::class.java) idStatusLabel.visibility = View.VISIBLE idTxStatus.visibility = View.VISIBLE - txStatus = if (sigs[0] == sigs[1]) { + if (sigs[0] == sigs[1]) { idTxStatus.setText(R.string.signed) - 1 } else { idTxStatus.setText(getString(R.string.partially_signed) + " (${sigs[0]}/${sigs[1]})") - 0 } } catch (e: PyException) { idStatusLabel.visibility = View.INVISIBLE idTxStatus.visibility = View.INVISIBLE - txStatus = -1 } } } +/* Check if the wallet can sign the transaction */ +fun canSign(tx: PyObject): Boolean { + return try { + !tx.callAttr("is_complete").toBoolean() and + daemonModel.wallet!!.callAttr("can_sign", tx).toBoolean() + } catch (e: PyException) { + false + } +} + +/* Check if the transaction is ready to be broadcasted */ +fun canBroadcast(tx: PyObject): Boolean { + return try { + tx.callAttr("is_complete").toBoolean() + } catch (e: PyException) { + false + } +} + /** * Sign a loaded transaction dialog. */ @@ -181,7 +197,7 @@ class SignPasswordDialog : PasswordDialog() { } override fun onPostExecute(result: Unit) { - if (coldLoadDialog.txStatus != 1) { + if (!canBroadcast(tx)) { coldLoadDialog.dismiss() copyToClipboard(tx.toString(), R.string.signed_transaction) } From cf1dcec6618678c1ca505fb1564aa6761b561aa1 Mon Sep 17 00:00:00 2001 From: Calin Culianu Date: Tue, 20 Apr 2021 08:05:43 -0400 Subject: [PATCH 056/208] Update servers.json Renamed "bitcoincash.quangld.com" -> "bchisbitcoin.com" --- electroncash/servers.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/electroncash/servers.json b/electroncash/servers.json index 5db92e0db58d..2b6b3eb253cd 100644 --- a/electroncash/servers.json +++ b/electroncash/servers.json @@ -35,7 +35,7 @@ "s": "50002", "version": "1.4.1" }, - "bitcoincash.quangld.com": { + "bchisbitcoin.com": { "pruning": "-", "s": "50002", "t": "50001", From 3306947fd3e4c53e755137b95c7e5c3ce715d7ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aldin=20Kova=C4=8Devi=C4=87?= Date: Wed, 21 Apr 2021 23:28:05 +0200 Subject: [PATCH 057/208] Android: Using 'get_tx_info' to get the status of the transaction. --- .../org/electroncash/electroncash3/ColdLoad.kt | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/android/app/src/main/java/org/electroncash/electroncash3/ColdLoad.kt b/android/app/src/main/java/org/electroncash/electroncash3/ColdLoad.kt index 8df174b8561e..1b1eafaca8bf 100644 --- a/android/app/src/main/java/org/electroncash/electroncash3/ColdLoad.kt +++ b/android/app/src/main/java/org/electroncash/electroncash3/ColdLoad.kt @@ -139,19 +139,20 @@ class ColdLoadDialog : AlertDialogFragment() { /** * Check if a loaded transaction is signed. * Displays the signing status below the raw TX field. - * (signed, partially signed, or invisible label, if the signatures are invalid) + * (signed, partially signed, or invisible label, if the transaction is invalid) */ private fun updateStatusText(tx: PyObject) { try { - val sigs = tx.callAttr("signature_count").toJava(IntArray::class.java) + val txInfo = daemonModel.wallet!!.callAttr("get_tx_info", tx) idStatusLabel.visibility = View.VISIBLE idTxStatus.visibility = View.VISIBLE - if (sigs[0] == sigs[1]) { - idTxStatus.setText(R.string.signed) + // Check if the transaction can be processed by this wallet or not + if (txInfo["amount"] == null) { + idTxStatus.setText(R.string.transaction_unrelated) } else { - idTxStatus.setText(getString(R.string.partially_signed) + " (${sigs[0]}/${sigs[1]})") + idTxStatus.setText(txInfo["status"].toString()) } } catch (e: PyException) { idStatusLabel.visibility = View.INVISIBLE @@ -185,7 +186,9 @@ fun canBroadcast(tx: PyObject): Boolean { class SignPasswordDialog : PasswordDialog() { val coldLoadDialog by lazy { targetFragment as ColdLoadDialog } - val tx by lazy { libTransaction.callAttr("Transaction", arguments!!.getString("tx")) } + val signSchnorr = daemonModel.walletType == "standard" // sign with Schnorr in standard wallets + + val tx by lazy { libTransaction.callAttr("Transaction", arguments!!.getString("tx"), signSchnorr) } val wallet = daemonModel.wallet!! override fun onPassword(password: String) { From f4d5118328923884c4b06812955633cf16cf9f2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aldin=20Kova=C4=8Devi=C4=87?= Date: Wed, 21 Apr 2021 23:45:12 +0200 Subject: [PATCH 058/208] Android: Minor text updates. --- android/app/src/main/res/menu/wallet.xml | 2 +- android/app/src/main/res/menu/wallet_kind.xml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/android/app/src/main/res/menu/wallet.xml b/android/app/src/main/res/menu/wallet.xml index b1a39e55f84c..9e6e2e74509e 100644 --- a/android/app/src/main/res/menu/wallet.xml +++ b/android/app/src/main/res/menu/wallet.xml @@ -20,7 +20,7 @@ + android:title="@string/information" /> diff --git a/android/app/src/main/res/menu/wallet_kind.xml b/android/app/src/main/res/menu/wallet_kind.xml index 8913e3030b0b..4dc1b8caab02 100644 --- a/android/app/src/main/res/menu/wallet_kind.xml +++ b/android/app/src/main/res/menu/wallet_kind.xml @@ -3,10 +3,10 @@ + android:title="@string/Standard_wallet" /> + android:title="@string/Multi_signature" /> \ No newline at end of file From 7ca9066c953fb86e469dbd431f31bc4c7d05863a Mon Sep 17 00:00:00 2001 From: Malcolm Smith Date: Thu, 22 Apr 2021 14:14:49 +0100 Subject: [PATCH 059/208] Android: fix duplicate requirement, add missing string --- android/README.md | 52 +++++++++++-------- android/app/build.gradle | 4 +- .../electroncash_gui/android/strings.py | 1 + 3 files changed, 32 insertions(+), 25 deletions(-) diff --git a/android/README.md b/android/README.md index 3b3bd6351192..cac677a019a8 100644 --- a/android/README.md +++ b/android/README.md @@ -1,9 +1,36 @@ # Electron Cash Android app +## Development + +To start developing the app, just open this directory in Android Studio. + +### Requirements + +You'll need to set up the following things before building the app: + +* Python must be on the PATH under the name `python3` on Linux/Mac, or `py` on Windows, and it + must have the packages listed in `build-requirements.txt`. +* The commands `xgettext` and `msgfmt` must be on the PATH. On Windows, the easiest way to + get these is to install MSYS2. + +### Strings + +Most user interface strings are reused from the desktop and iOS apps. Android-specific strings +should be added to `app/src/main/python/electroncash_gui/android/strings.py`. + +The Gradle task `generateStrings` takes all localized strings in the repository, and their +translations from Crowdin, and converts them into `strings.xml` format so they can be accessed +through the Android resource API. This task is run automatically the first time you build the +app, and whenever you edit the `strings.py` file mentioned above. If you need to pick up new +strings from anywhere else in the repository, run the task `regenerateStrings`. + +The Android string IDs are generated from the first 2 words of each string, plus as many more +words as necessary to make them unique. So if any of the source strings change, you may need to +update ID references in the code. + ## Release -The Android app can be built on any OS which can run the Android development tools. However, -the following automated process is available for Linux x86-64: +For public releases, the following reproducible build process can be run on Linux x86-64: If necessary, install Docker using the [instructions on its website](https://docs.docker.com/install/#supported-platforms). @@ -18,24 +45,3 @@ following configuration: Run `build.sh`. The APK will be generated in `release` in this directory. Between builds it may be helpful to free up disk space with the command `docker system prune`. - -## Development - -To start developing the app, just open this directory in Android Studio. - -### Strings - -For user interface text, the app uses the standard Android string resource system. The -`strings.xml` files are generated by the Gradle task `generateStrings`, which in turn calls the -script `contrib/make_locale` to obtain strings from elsewhere in the repository and Crowdin. - -Android-specific strings should be added to -`app/src/main/python/electroncash_gui/android/strings.py`. - -`generateStrings` is run automatically the first time you build the app, and whenever you edit -`strings.py`. If you need to pick up new strings from any of the other source files, run the -task `regenerateStrings`. - -The Android string IDs are generated from the first 2 words of each string, plus as many more -words as necessary to make them unique. So if any of the source strings change, you may need to -update ID references in the code. diff --git a/android/app/build.gradle b/android/app/build.gradle index 6d0f845b5656..2abfee798858 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -150,7 +150,7 @@ afterEvaluate { dependsOn ("deleteStrings", "generateStrings") } - // Remove unnecessary requirements (#2162). + // Remove unnecessary or duplicate requirements. task("generateRequirementsTxt") { inputs.file "$REPO_ROOT/contrib/deterministic-build/requirements.txt" outputs.file REQUIREMENTS_TXT @@ -160,7 +160,7 @@ afterEvaluate { file(inputs.files.singleFile).eachLine { line -> buffer.append(line + "\n") if (!line.endsWith("\\")) { - if (!(buffer =~ /^(pip|QDarkStyle|setuptools|wheel)==/)) { + if (!(buffer =~ /^(cffi|pip|QDarkStyle|setuptools|wheel)==/)) { output.print(buffer) } buffer.setLength(0) diff --git a/android/app/src/main/python/electroncash_gui/android/strings.py b/android/app/src/main/python/electroncash_gui/android/strings.py index ce95c76e93d2..9cbd216be92c 100644 --- a/android/app/src/main/python/electroncash_gui/android/strings.py +++ b/android/app/src/main/python/electroncash_gui/android/strings.py @@ -14,6 +14,7 @@ _("Are you sure you want to delete your wallet \'%s\'?") _("BIP39 seed") _("Block explorer") +_("%s bytes") _("Cannot specify private keys and addresses in the same wallet.") _("Change password") _("Close wallet") From a64a434ec9e04a87b993fd32f05427e7dec343f4 Mon Sep 17 00:00:00 2001 From: Malcolm Smith Date: Mon, 26 Apr 2021 10:40:07 +0100 Subject: [PATCH 060/208] Make make_locale check exit status of gettext commands --- contrib/make_locale | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/contrib/make_locale b/contrib/make_locale index f6752c2b29bd..421a25e3336a 100755 --- a/contrib/make_locale +++ b/contrib/make_locale @@ -1,16 +1,22 @@ #!/usr/bin/env python3 + +import glob +import io +import itertools import os from os.path import isdir, join +from pathlib import Path +import requests +import shlex +from subprocess import check_call import sys -import io import zipfile -import requests -import glob -import itertools -from pathlib import Path assert len(sys.argv) < 3 +def run(cmd): + check_call(shlex.split(cmd)) + original_dir = os.getcwd() os.chdir(os.path.dirname(os.path.realpath(__file__))) os.chdir('..') @@ -45,7 +51,7 @@ if not os.path.exists('electroncash/locale'): os.mkdir('electroncash/locale') cmd = 'xgettext -s --from-code UTF-8 --language Python --no-wrap -f app.fil --keyword=pgettext:1c,2 --keyword=npgettext:1c,2,3 -c --output=electroncash/locale/messages.pot' print('Generate template') -os.system(cmd) +run(cmd) os.chdir('electroncash') @@ -102,4 +108,4 @@ for lang in os.listdir('locale'): os.mkdir(msg_dir) cmd = 'msgfmt --output-file="{0}/electron-cash.mo" "{0}/electron-cash.po"'.format(msg_dir) print('Installing', lang) - os.system(cmd) + run(cmd) From ce56d60d56e5480f325f3b12eb4259d90827721f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aldin=20Kova=C4=8Devi=C4=87?= Date: Mon, 24 May 2021 23:45:47 +0200 Subject: [PATCH 061/208] Removed unused lines and layout elements. --- .../src/main/java/org/electroncash/electroncash3/ColdLoad.kt | 4 +--- .../app/src/main/java/org/electroncash/electroncash3/Send.kt | 3 +-- android/app/src/main/res/layout/choose_keystore.xml | 5 ----- android/app/src/main/res/layout/load.xml | 3 +-- 4 files changed, 3 insertions(+), 12 deletions(-) diff --git a/android/app/src/main/java/org/electroncash/electroncash3/ColdLoad.kt b/android/app/src/main/java/org/electroncash/electroncash3/ColdLoad.kt index 1b1eafaca8bf..784cc0353ae8 100644 --- a/android/app/src/main/java/org/electroncash/electroncash3/ColdLoad.kt +++ b/android/app/src/main/java/org/electroncash/electroncash3/ColdLoad.kt @@ -22,8 +22,6 @@ val libTransaction by lazy { libMod("transaction") } // Valid transaction quickly show up in transactions. class ColdLoadDialog : AlertDialogFragment() { - /* 1 = signed, 0 = partially signed, -1 = invalid */ - var txStatus: Int? = null override fun onBuildDialog(builder: AlertDialog.Builder) { builder.setTitle(R.string.load_transaction) @@ -186,7 +184,7 @@ fun canBroadcast(tx: PyObject): Boolean { class SignPasswordDialog : PasswordDialog() { val coldLoadDialog by lazy { targetFragment as ColdLoadDialog } - val signSchnorr = daemonModel.walletType == "standard" // sign with Schnorr in standard wallets + val signSchnorr = daemonModel.walletType == "standard" val tx by lazy { libTransaction.callAttr("Transaction", arguments!!.getString("tx"), signSchnorr) } val wallet = daemonModel.wallet!! diff --git a/android/app/src/main/java/org/electroncash/electroncash3/Send.kt b/android/app/src/main/java/org/electroncash/electroncash3/Send.kt index f8be7d4132bb..b2968ac8eaf6 100644 --- a/android/app/src/main/java/org/electroncash/electroncash3/Send.kt +++ b/android/app/src/main/java/org/electroncash/electroncash3/Send.kt @@ -3,7 +3,6 @@ package org.electroncash.electroncash3 import android.content.Intent import android.os.Bundle import android.text.Editable -import android.util.Log import android.view.Menu import android.view.MenuItem import android.widget.SeekBar @@ -200,7 +199,7 @@ class SendDialog : TaskLauncherDialog() { val inputs = wallet.callAttr("get_spendable_coins", null, daemonModel.config, Kwarg("isInvoice", pr != null)) return try { - val signSchnorr = daemonModel.walletType == "standard" // sign with Schnorr in standard wallets + val signSchnorr = daemonModel.walletType == "standard" TxResult(wallet.callAttr("make_unsigned_transaction", inputs, outputs, daemonModel.config, Kwarg("sign_schnorr", signSchnorr)), isDummy) diff --git a/android/app/src/main/res/layout/choose_keystore.xml b/android/app/src/main/res/layout/choose_keystore.xml index 2ac3829be8cb..7d9b0201c12e 100644 --- a/android/app/src/main/res/layout/choose_keystore.xml +++ b/android/app/src/main/res/layout/choose_keystore.xml @@ -32,10 +32,5 @@ app:layout_constraintStart_toStartOf="@+id/keystoreDesc" app:layout_constraintTop_toBottomOf="@+id/keystoreDesc" /> - \ No newline at end of file diff --git a/android/app/src/main/res/layout/load.xml b/android/app/src/main/res/layout/load.xml index 9ff31aea8712..89a7142691d5 100644 --- a/android/app/src/main/res/layout/load.xml +++ b/android/app/src/main/res/layout/load.xml @@ -24,8 +24,6 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent"/> - - + Date: Wed, 26 May 2021 01:26:19 +0100 Subject: [PATCH 062/208] Android: update to Gradle 6.5 and Android Gradle plugin 4.1.2: Previous version was incompatible with Java 11, which is used by Android Studio 4.2. --- android/build.gradle | 2 +- android/gradle/wrapper/gradle-wrapper.properties | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/android/build.gradle b/android/build.gradle index b8de1c111736..65e5aa21dc0e 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -12,7 +12,7 @@ buildscript { } } dependencies { - classpath "com.android.tools.build:gradle:3.5.0" + classpath "com.android.tools.build:gradle:4.1.2" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion" // This version is for Electron Cash only. Other apps should use one of the versions diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index 0b36bab0e40e..b5fbe5fdba65 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip From fec98cd4b277d98144ebd18e974b4b525ee9de6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aldin=20Kova=C4=8Devi=C4=87?= Date: Thu, 27 May 2021 21:28:25 +0200 Subject: [PATCH 063/208] Made 'sign_schnorr' into a Kwarg parameter --- .../src/main/java/org/electroncash/electroncash3/ColdLoad.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/android/app/src/main/java/org/electroncash/electroncash3/ColdLoad.kt b/android/app/src/main/java/org/electroncash/electroncash3/ColdLoad.kt index 784cc0353ae8..1a6e9c13c0af 100644 --- a/android/app/src/main/java/org/electroncash/electroncash3/ColdLoad.kt +++ b/android/app/src/main/java/org/electroncash/electroncash3/ColdLoad.kt @@ -6,6 +6,7 @@ import android.os.Bundle import android.view.View import android.widget.Toast import androidx.appcompat.app.AlertDialog +import com.chaquo.python.Kwarg import com.chaquo.python.PyException import com.chaquo.python.PyObject import com.google.zxing.integration.android.IntentIntegrator @@ -186,7 +187,8 @@ class SignPasswordDialog : PasswordDialog() { val coldLoadDialog by lazy { targetFragment as ColdLoadDialog } val signSchnorr = daemonModel.walletType == "standard" - val tx by lazy { libTransaction.callAttr("Transaction", arguments!!.getString("tx"), signSchnorr) } + val tx by lazy { libTransaction.callAttr("Transaction", arguments!!.getString("tx"), + Kwarg("sign_schnorr", signSchnorr)) } val wallet = daemonModel.wallet!! override fun onPassword(password: String) { From 01ee099eb40145e2680a1eab0afdd86188abea1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aldin=20Kova=C4=8Devi=C4=87?= Date: Thu, 27 May 2021 21:47:14 +0200 Subject: [PATCH 064/208] Showing 'empty' and 'invalid' labels instead of having an invisible label in ColdLoad. --- .../main/java/org/electroncash/electroncash3/ColdLoad.kt | 6 +----- android/app/src/main/res/layout/load.xml | 6 +++--- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/android/app/src/main/java/org/electroncash/electroncash3/ColdLoad.kt b/android/app/src/main/java/org/electroncash/electroncash3/ColdLoad.kt index 1a6e9c13c0af..33f8e3085997 100644 --- a/android/app/src/main/java/org/electroncash/electroncash3/ColdLoad.kt +++ b/android/app/src/main/java/org/electroncash/electroncash3/ColdLoad.kt @@ -144,9 +144,6 @@ class ColdLoadDialog : AlertDialogFragment() { try { val txInfo = daemonModel.wallet!!.callAttr("get_tx_info", tx) - idStatusLabel.visibility = View.VISIBLE - idTxStatus.visibility = View.VISIBLE - // Check if the transaction can be processed by this wallet or not if (txInfo["amount"] == null) { idTxStatus.setText(R.string.transaction_unrelated) @@ -154,8 +151,7 @@ class ColdLoadDialog : AlertDialogFragment() { idTxStatus.setText(txInfo["status"].toString()) } } catch (e: PyException) { - idStatusLabel.visibility = View.INVISIBLE - idTxStatus.visibility = View.INVISIBLE + idTxStatus.setText(R.string.invalid) } } } diff --git a/android/app/src/main/res/layout/load.xml b/android/app/src/main/res/layout/load.xml index 89a7142691d5..f4bb2584d757 100644 --- a/android/app/src/main/res/layout/load.xml +++ b/android/app/src/main/res/layout/load.xml @@ -67,7 +67,7 @@ android:layout_marginStart="24dp" android:layout_marginTop="10dp" android:text="@string/status_" - android:visibility="invisible" + android:visibility="visible" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/etTransaction" /> @@ -78,8 +78,8 @@ android:layout_height="wrap_content" android:layout_marginStart="10dp" android:layout_marginTop="10dp" - android:text="@string/status" - android:visibility="invisible" + android:text="@string/empty" + android:visibility="visible" app:layout_constraintStart_toEndOf="@+id/idStatusLabel" app:layout_constraintTop_toBottomOf="@+id/etTransaction" /> From ce5f2561c3f69bda2e2adde75a66d3d246b5adfe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aldin=20Kova=C4=8Devi=C4=87?= Date: Thu, 27 May 2021 21:54:07 +0200 Subject: [PATCH 065/208] Removed double calling of isNotBlank on hex transaction text field. --- .../main/java/org/electroncash/electroncash3/ColdLoad.kt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/android/app/src/main/java/org/electroncash/electroncash3/ColdLoad.kt b/android/app/src/main/java/org/electroncash/electroncash3/ColdLoad.kt index 33f8e3085997..8df52892e288 100644 --- a/android/app/src/main/java/org/electroncash/electroncash3/ColdLoad.kt +++ b/android/app/src/main/java/org/electroncash/electroncash3/ColdLoad.kt @@ -38,6 +38,7 @@ class ColdLoadDialog : AlertDialogFragment() { updateUI() dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener { onOK() } + dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = false dialog.getButton(AlertDialog.BUTTON_NEUTRAL).setOnClickListener { scanQR(this) } btnPaste.setOnClickListener { val clipdata = getSystemService(ClipboardManager::class).primaryClip @@ -50,11 +51,11 @@ class ColdLoadDialog : AlertDialogFragment() { private fun updateUI() { val currenttext = etTransaction.text - //checks if text is blank. further validations can be added here - dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = currenttext.isNotBlank() - val tx: PyObject + + //checks if text is blank. further validations can be added here if (currenttext.isNotBlank()) { + dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = true tx = libTransaction.callAttr("Transaction", etTransaction.text.toString()) updateStatusText(tx) From 14d677ca69e626b9a4f50595df39c228374a529b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aldin=20Kova=C4=8Devi=C4=87?= Date: Thu, 27 May 2021 22:21:20 +0200 Subject: [PATCH 066/208] Layout fixes in load.xml --- android/app/src/main/res/layout/load.xml | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/android/app/src/main/res/layout/load.xml b/android/app/src/main/res/layout/load.xml index f4bb2584d757..e1dabdf48795 100644 --- a/android/app/src/main/res/layout/load.xml +++ b/android/app/src/main/res/layout/load.xml @@ -46,11 +46,9 @@ style="@style/FAB.Dialog" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_marginEnd="8dp" app:layout_constraintBottom_toBottomOf="@+id/etTransaction" app:layout_constraintEnd_toEndOf="@+id/textView4" app:layout_constraintTop_toTopOf="@+id/etTransaction" - app:layout_constraintVertical_bias="0.0" app:srcCompat="@drawable/ic_paste_24dp" /> + app:layout_constraintBaseline_toBaselineOf="@+id/idTxStatus" + app:layout_constraintStart_toStartOf="parent" /> Date: Thu, 27 May 2021 23:05:50 +0200 Subject: [PATCH 067/208] Removed Base43 object and replaced it with a call to bitcoin.py's base_decode(). --- .../org/electroncash/electroncash3/Base43.kt | 113 ------------------ .../electroncash/electroncash3/ColdLoad.kt | 4 +- .../org/electroncash/electroncash3/Util.kt | 8 ++ 3 files changed, 10 insertions(+), 115 deletions(-) delete mode 100644 android/app/src/main/java/org/electroncash/electroncash3/Base43.kt diff --git a/android/app/src/main/java/org/electroncash/electroncash3/Base43.kt b/android/app/src/main/java/org/electroncash/electroncash3/Base43.kt deleted file mode 100644 index ffb4df0d700d..000000000000 --- a/android/app/src/main/java/org/electroncash/electroncash3/Base43.kt +++ /dev/null @@ -1,113 +0,0 @@ -package org.electroncash.electroncash3 - -import kotlin.text.Charsets.US_ASCII - -/** - * Base43 conversion, to use with transaction QRs generated by Electron Cash. - * Adjusted from the Java implementation by Andreas Schildbach at - * https://www.javatips.net/api/peercoin-android-wallet-master/wallet/src/com/matthewmitchell/peercoin_android_wallet/util/Base43.java - */ -object Base43 { - - val alphabet: CharArray = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ$*+-./:".toCharArray() - val indexes: IntArray = IntArray(128) - fun ByteArray.toHexString() = joinToString("") { "%02x".format(it) } - - init { - for (i in indexes.indices) { - indexes[i] = -1 - } - for (i in alphabet.indices) { - indexes[alphabet[i].toInt()] = i - } - } - - fun encode(input: ByteArray): String { - var input = input - if (input.isEmpty()) return "" - input = copyOfRange(input, 0, input.size) - - // Count leading zeroes. - var zeroCount = 0 - while (zeroCount < input.size && input[zeroCount].toInt() == 0) ++zeroCount - - // The actual encoding. - val temp = ByteArray(input.size * 2) - var j = temp.size - var startAt = zeroCount - while (startAt < input.size) { - val mod: Byte = divmod43(input, startAt) - if (input[startAt].toInt() == 0) ++startAt - temp[--j] = alphabet[mod.toInt()].toByte() - } - - // Strip extra '1' if there are some after decoding. - while (j < temp.size && temp[j].toChar() == alphabet[0]) ++j - - // Add as many leading '1' as there were leading zeros. - while (--zeroCount >= 0) temp[--j] = alphabet[0].toByte() - val output: ByteArray = copyOfRange(temp, j, temp.size) - return String(output, US_ASCII) - } - - @Throws(IllegalArgumentException::class) - fun decode(input: String): String { - if (input.isEmpty()) return "" - val input43 = ByteArray(input.length) - // Transform the String to a base43 byte sequence - for (i in input.indices) { - val c = input[i] - var digit43 = -1 - if (c.toInt() in 0..127) digit43 = indexes[c.toInt()] - require(digit43 >= 0) { "Illegal character $c at $i" } - input43[i] = digit43.toByte() - } - - // Count leading zeroes - var zeroCount = 0 - while (zeroCount < input43.size && input43[zeroCount].toInt() == 0) ++zeroCount - - // The encoding - val temp = ByteArray(input.length) - var j = temp.size - var startAt = zeroCount - while (startAt < input43.size) { - val mod: Byte = divmod256(input43, startAt) - if (input43[startAt].toInt() == 0) ++startAt - temp[--j] = mod - } - - // Do no add extra leading zeroes, move j to first non null byte. - while (j < temp.size && temp[j].toInt() == 0) ++j - - return copyOfRange(temp, j - zeroCount, temp.size).toHexString() - } - - private fun divmod43(number: ByteArray, startAt: Int): Byte { - var remainder = 0 - for (i in startAt until number.size) { - val digit256 = number[i].toInt() and 0xFF - val temp = remainder * 256 + digit256 - number[i] = (temp / 43).toByte() - remainder = temp % 43 - } - return remainder.toByte() - } - - private fun divmod256(number43: ByteArray, startAt: Int): Byte { - var remainder = 0 - for (i in startAt until number43.size) { - val digit58 = number43[i].toInt() and 0xFF - val temp = remainder * 43 + digit58 - number43[i] = (temp / 256).toByte() - remainder = temp % 256 - } - return remainder.toByte() - } - - private fun copyOfRange(source: ByteArray, from: Int, to: Int): ByteArray { - val range = ByteArray(to - from) - System.arraycopy(source, from, range, 0, range.size) - return range - } -} \ No newline at end of file diff --git a/android/app/src/main/java/org/electroncash/electroncash3/ColdLoad.kt b/android/app/src/main/java/org/electroncash/electroncash3/ColdLoad.kt index 8df52892e288..b3dc6d4451af 100644 --- a/android/app/src/main/java/org/electroncash/electroncash3/ColdLoad.kt +++ b/android/app/src/main/java/org/electroncash/electroncash3/ColdLoad.kt @@ -79,8 +79,8 @@ class ColdLoadDialog : AlertDialogFragment() { // Try to decode the QR content as Base43; if that fails, treat it as is val txHex: String = try { - Base43.decode(result.contents) - } catch (e: IllegalArgumentException) { + baseDecode(result.contents, 43) + } catch (e: PyException) { result.contents } diff --git a/android/app/src/main/java/org/electroncash/electroncash3/Util.kt b/android/app/src/main/java/org/electroncash/electroncash3/Util.kt index 0c6ea176d965..1ecf0b85c8ec 100644 --- a/android/app/src/main/java/org/electroncash/electroncash3/Util.kt +++ b/android/app/src/main/java/org/electroncash/electroncash3/Util.kt @@ -250,3 +250,11 @@ private fun menuToList(menu: Menu): List { } return result } + +/** + * Interface to base_encode/decode in bitcoin.py + */ +fun baseDecode(s: String, base: Int): String { + return libBitcoin.callAttr("base_decode", s, null, base) + .callAttr("hex").toString() +} From 0083065ae8aee23b946e4ca206767409a9c6fd0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aldin=20Kova=C4=8Devi=C4=87?= Date: Fri, 28 May 2021 00:24:44 +0200 Subject: [PATCH 068/208] Layout and element ID updates. --- .../electroncash/electroncash3/ColdLoad.kt | 2 +- .../org/electroncash/electroncash3/Main.kt | 5 +--- .../electroncash/electroncash3/NewWallet.kt | 6 ++-- .../main/res/layout/wallet_information.xml | 28 ++++++++----------- .../app/src/main/res/layout/wallet_new.xml | 8 +++--- android/app/src/main/res/menu/wallet.xml | 2 +- 6 files changed, 22 insertions(+), 29 deletions(-) diff --git a/android/app/src/main/java/org/electroncash/electroncash3/ColdLoad.kt b/android/app/src/main/java/org/electroncash/electroncash3/ColdLoad.kt index b3dc6d4451af..a74830b11879 100644 --- a/android/app/src/main/java/org/electroncash/electroncash3/ColdLoad.kt +++ b/android/app/src/main/java/org/electroncash/electroncash3/ColdLoad.kt @@ -139,7 +139,7 @@ class ColdLoadDialog : AlertDialogFragment() { /** * Check if a loaded transaction is signed. * Displays the signing status below the raw TX field. - * (signed, partially signed, or invisible label, if the transaction is invalid) + * (signed, partially signed, empty or invalid) */ private fun updateStatusText(tx: PyObject) { try { diff --git a/android/app/src/main/java/org/electroncash/electroncash3/Main.kt b/android/app/src/main/java/org/electroncash/electroncash3/Main.kt index d12a2226f537..f7540e14a707 100644 --- a/android/app/src/main/java/org/electroncash/electroncash3/Main.kt +++ b/android/app/src/main/java/org/electroncash/electroncash3/Main.kt @@ -4,9 +4,6 @@ package org.electroncash.electroncash3 import android.annotation.SuppressLint import android.app.Activity -import android.content.ClipData -import android.content.ClipboardManager -import android.content.Context import android.content.Intent import android.content.res.Configuration import android.net.Uri @@ -240,7 +237,7 @@ class MainActivity : AppCompatActivity(R.layout.main) { } R.id.menuChangePassword -> showDialog(this, PasswordChangeDialog()) R.id.menuShowSeed -> { showDialog(this, SeedPasswordDialog()) } - R.id.menuMasterPublicKey -> { showDialog(this, WalletInformationDialog()) } + R.id.menuWalletInformation -> { showDialog(this, WalletInformationDialog()) } R.id.menuExportSigned -> { try { showDialog(this, SendDialog().apply { diff --git a/android/app/src/main/java/org/electroncash/electroncash3/NewWallet.kt b/android/app/src/main/java/org/electroncash/electroncash3/NewWallet.kt index 02222209a146..123495ca9903 100644 --- a/android/app/src/main/java/org/electroncash/electroncash3/NewWallet.kt +++ b/android/app/src/main/java/org/electroncash/electroncash3/NewWallet.kt @@ -41,7 +41,7 @@ class NewWalletDialog1 : AlertDialogFragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - spnWalletKind.adapter = MenuAdapter(context!!, R.menu.wallet_kind) + spnWalletType.adapter = MenuAdapter(context!!, R.menu.wallet_kind) } override fun onShowDialog() { @@ -56,7 +56,7 @@ class NewWalletDialog1 : AlertDialogFragment() { putString("password", password) } - when (spnWalletKind.selectedItemId.toInt()) { + when (spnWalletType.selectedItemId.toInt()) { R.id.menuStandardWallet -> { nextDialog = KeystoreDialog() showDialog(this, nextDialog.apply { setArguments(arguments) }) @@ -66,7 +66,7 @@ class NewWalletDialog1 : AlertDialogFragment() { showDialog(this, nextDialog.apply { setArguments(arguments) }) } else -> { - throw Exception("Unknown item: ${spnWalletKind.selectedItem}") + throw Exception("Unknown item: ${spnWalletType.selectedItem}") } } diff --git a/android/app/src/main/res/layout/wallet_information.xml b/android/app/src/main/res/layout/wallet_information.xml index 15206f09728f..02dc6fdcc3be 100644 --- a/android/app/src/main/res/layout/wallet_information.xml +++ b/android/app/src/main/res/layout/wallet_information.xml @@ -15,38 +15,34 @@ android:layout_width="0dp" android:layout_height="21dp" android:layout_marginStart="24dp" - android:layout_marginTop="24dp" android:gravity="fill_horizontal" android:text="@string/wallet_name" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toTopOf="parent" /> + app:layout_constraintBaseline_toBaselineOf="@+id/idWalletName" + app:layout_constraintStart_toStartOf="parent" /> + app:layout_constraintBaseline_toBaselineOf="@+id/idWalletType" + app:layout_constraintStart_toStartOf="parent" /> + app:layout_constraintBaseline_toBaselineOf="@+id/idScriptType" + app:layout_constraintStart_toStartOf="parent" /> @@ -116,8 +112,8 @@ style="@style/Medium" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_marginStart="10dp" - android:layout_marginTop="10dp" + android:layout_marginStart="6dp" + android:layout_marginTop="6dp" android:text="@string/script_type" app:layout_constraintStart_toEndOf="@id/idBarrier2" app:layout_constraintTop_toBottomOf="@+id/idWalletType" /> diff --git a/android/app/src/main/res/layout/wallet_new.xml b/android/app/src/main/res/layout/wallet_new.xml index 38ac05c7710d..135eccef7cdd 100644 --- a/android/app/src/main/res/layout/wallet_new.xml +++ b/android/app/src/main/res/layout/wallet_new.xml @@ -15,8 +15,8 @@ android:layout_height="wrap_content" android:layout_marginTop="12dp" android:text="@string/wallet_name" - app:layout_constraintStart_toStartOf="@+id/spnWalletKind" - app:layout_constraintTop_toBottomOf="@+id/spnWalletKind" /> + app:layout_constraintStart_toStartOf="@+id/spnWalletType" + app:layout_constraintTop_toBottomOf="@+id/spnWalletType" /> Date: Fri, 28 May 2021 00:57:36 +0200 Subject: [PATCH 069/208] Moved the copy button in wallet_information.xml, fixed layout order and constraints. --- .../org/electroncash/electroncash3/Main.kt | 3 +- .../main/res/layout/wallet_information.xml | 203 ++++++++++-------- 2 files changed, 111 insertions(+), 95 deletions(-) diff --git a/android/app/src/main/java/org/electroncash/electroncash3/Main.kt b/android/app/src/main/java/org/electroncash/electroncash3/Main.kt index f7540e14a707..725c21df7497 100644 --- a/android/app/src/main/java/org/electroncash/electroncash3/Main.kt +++ b/android/app/src/main/java/org/electroncash/electroncash3/Main.kt @@ -676,8 +676,7 @@ class SeedDialog : AlertDialogFragment() { class WalletInformationDialog : AlertDialogFragment() { override fun onBuildDialog(builder: AlertDialog.Builder) { - builder.setTitle(R.string.wallet_information) - .setView(R.layout.wallet_information) + builder.setView(R.layout.wallet_information) .setPositiveButton(android.R.string.ok, null) } diff --git a/android/app/src/main/res/layout/wallet_information.xml b/android/app/src/main/res/layout/wallet_information.xml index 02dc6fdcc3be..d6ec39b1b188 100644 --- a/android/app/src/main/res/layout/wallet_information.xml +++ b/android/app/src/main/res/layout/wallet_information.xml @@ -1,122 +1,139 @@ - + android:layout_height="wrap_content" + android:orientation="vertical"> - - - - - - - - + app:layout_constraintTop_toTopOf="parent" /> + - + - + - + - + - - \ No newline at end of file + + + + + + + + + + + + + + + + + \ No newline at end of file From b86c8555602637118cd3de6e9c86cf468def8954 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aldin=20Kova=C4=8Devi=C4=87?= Date: Fri, 28 May 2021 18:04:05 +0200 Subject: [PATCH 070/208] Changed 'Information' to 'Wallet information' and used proper capitalization. --- .../app/src/main/python/electroncash_gui/android/strings.py | 1 + android/app/src/main/res/layout/wallet_information.xml | 3 +-- android/app/src/main/res/menu/wallet.xml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/android/app/src/main/python/electroncash_gui/android/strings.py b/android/app/src/main/python/electroncash_gui/android/strings.py index 9cbd216be92c..cda94d38eb3a 100644 --- a/android/app/src/main/python/electroncash_gui/android/strings.py +++ b/android/app/src/main/python/electroncash_gui/android/strings.py @@ -55,6 +55,7 @@ _("%1$d tx (%2$d unverified)") _("Type, paste, or scan a valid signed transaction in hex format below:") _("Use a master key") +_("Wallet information") _("Wallet name is too long") _("Wallet names cannot contain the '/' character. Please enter a different wallet name to proceed.") _("Wallet exported successfully") diff --git a/android/app/src/main/res/layout/wallet_information.xml b/android/app/src/main/res/layout/wallet_information.xml index d6ec39b1b188..150c9d43bc70 100644 --- a/android/app/src/main/res/layout/wallet_information.xml +++ b/android/app/src/main/res/layout/wallet_information.xml @@ -17,8 +17,7 @@ android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginStart="24dp" - android:layout_marginLeft="24dp" - android:text="@string/wallet_information" + android:text="@string/Wallet_information" app:layout_constraintEnd_toStartOf="@+id/fabCopyMasterKey2" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> diff --git a/android/app/src/main/res/menu/wallet.xml b/android/app/src/main/res/menu/wallet.xml index f8833facba21..5c2e99c2cf8a 100644 --- a/android/app/src/main/res/menu/wallet.xml +++ b/android/app/src/main/res/menu/wallet.xml @@ -20,7 +20,7 @@ + android:title="@string/Wallet_information" /> From 998090dd9240db2fd5e9a7884f4e1682849588ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aldin=20Kova=C4=8Devi=C4=87?= Date: Fri, 28 May 2021 18:22:48 +0200 Subject: [PATCH 071/208] Moved the copy button for 'Master public key' and fixed text case. --- .../org/electroncash/electroncash3/Main.kt | 2 +- .../electroncash/electroncash3/NewWallet.kt | 5 +- .../electroncash_gui/android/strings.py | 1 + .../src/main/res/layout/show_master_key.xml | 97 +++++++++++-------- 4 files changed, 62 insertions(+), 43 deletions(-) diff --git a/android/app/src/main/java/org/electroncash/electroncash3/Main.kt b/android/app/src/main/java/org/electroncash/electroncash3/Main.kt index 725c21df7497..729d98c65d99 100644 --- a/android/app/src/main/java/org/electroncash/electroncash3/Main.kt +++ b/android/app/src/main/java/org/electroncash/electroncash3/Main.kt @@ -685,7 +685,7 @@ class WalletInformationDialog : AlertDialogFragment() { fabCopyMasterKey2.setOnClickListener { val textToCopy = walletMasterKey.text - copyToClipboard(textToCopy, R.string.master_public_key) + copyToClipboard(textToCopy, R.string.Master_public) } } diff --git a/android/app/src/main/java/org/electroncash/electroncash3/NewWallet.kt b/android/app/src/main/java/org/electroncash/electroncash3/NewWallet.kt index 123495ca9903..34a058b883d7 100644 --- a/android/app/src/main/java/org/electroncash/electroncash3/NewWallet.kt +++ b/android/app/src/main/java/org/electroncash/electroncash3/NewWallet.kt @@ -561,8 +561,7 @@ class CosignerDialog : AlertDialogFragment() { */ class MasterPublicKeyDialog : AlertDialogFragment() { override fun onBuildDialog(builder: AlertDialog.Builder) { - builder.setTitle(R.string.master_public_key) - .setView(R.layout.show_master_key) + builder.setView(R.layout.show_master_key) .setPositiveButton(R.string.next, null) .setNegativeButton(R.string.back, null) } @@ -571,7 +570,7 @@ class MasterPublicKeyDialog : AlertDialogFragment() { super.onViewCreated(view, savedInstanceState) fabCopyMasterKey.setOnClickListener { - copyToClipboard(walletMasterKey.text, R.string.master_public_key) + copyToClipboard(walletMasterKey.text, R.string.Master_public) } } diff --git a/android/app/src/main/python/electroncash_gui/android/strings.py b/android/app/src/main/python/electroncash_gui/android/strings.py index cda94d38eb3a..7e4fd804c157 100644 --- a/android/app/src/main/python/electroncash_gui/android/strings.py +++ b/android/app/src/main/python/electroncash_gui/android/strings.py @@ -36,6 +36,7 @@ _("Invalid address") _("Load transaction") _("Made with Chaquopy, the Python SDK for Android.") +_("Master public key") _("New password") _("New wallet") _("No wallet is open.") diff --git a/android/app/src/main/res/layout/show_master_key.xml b/android/app/src/main/res/layout/show_master_key.xml index 7a8da6c556a6..0068058b658d 100644 --- a/android/app/src/main/res/layout/show_master_key.xml +++ b/android/app/src/main/res/layout/show_master_key.xml @@ -1,64 +1,83 @@ - + android:layout_height="wrap_content" + android:orientation="vertical"> + android:layout_height="match_parent"> - - - - + app:layout_constraintTop_toTopOf="parent" /> - \ No newline at end of file + + + + + + + + + + + + + \ No newline at end of file From e7d15ace380fc535c3856074c060f7bddb08bfc1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aldin=20Kova=C4=8Devi=C4=87?= Date: Fri, 28 May 2021 18:49:45 +0200 Subject: [PATCH 072/208] Fixed vertical margins on newly added layouts; fixed the order of elements in layout XML files to match the visual layout. --- .../src/main/res/layout/choose_keystore.xml | 3 +- .../main/res/layout/multisig_cosigners.xml | 10 ++-- .../src/main/res/layout/show_master_key.xml | 51 ++++++++++--------- .../app/src/main/res/layout/wallet_new.xml | 39 +++++++------- 4 files changed, 51 insertions(+), 52 deletions(-) diff --git a/android/app/src/main/res/layout/choose_keystore.xml b/android/app/src/main/res/layout/choose_keystore.xml index 7d9b0201c12e..23a02fb62c2d 100644 --- a/android/app/src/main/res/layout/choose_keystore.xml +++ b/android/app/src/main/res/layout/choose_keystore.xml @@ -15,7 +15,7 @@ android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginStart="24dp" - android:layout_marginTop="24dp" + android:layout_marginTop="8dp" android:layout_marginEnd="24dp" android:text="@string/do_you_want_to_create" app:layout_constraintEnd_toEndOf="parent" @@ -26,7 +26,6 @@ android:id="@+id/spnType" android:layout_width="0dp" android:layout_height="wrap_content" - android:layout_marginTop="12dp" android:layout_marginEnd="24dp" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="@+id/keystoreDesc" diff --git a/android/app/src/main/res/layout/multisig_cosigners.xml b/android/app/src/main/res/layout/multisig_cosigners.xml index b21573e75aaa..d3096f140716 100644 --- a/android/app/src/main/res/layout/multisig_cosigners.xml +++ b/android/app/src/main/res/layout/multisig_cosigners.xml @@ -15,7 +15,7 @@ android:layout_width="0dp" android:layout_height="39dp" android:layout_marginStart="24dp" - android:layout_marginTop="24dp" + android:layout_marginTop="8dp" android:layout_marginEnd="24dp" android:text="@string/choose_the" app:layout_constraintEnd_toEndOf="parent" @@ -27,7 +27,7 @@ android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginStart="24dp" - android:layout_marginTop="14dp" + android:layout_marginTop="12dp" android:layout_marginEnd="24dp" android:text="@string/from_cosigners" app:layout_constraintEnd_toEndOf="parent" @@ -38,7 +38,7 @@ android:id="@+id/sbCosigners" android:layout_width="0dp" android:layout_height="wrap_content" - android:layout_marginTop="10dp" + android:layout_marginTop="8dp" android:layout_marginEnd="24dp" android:max="15" android:progress="2" @@ -51,7 +51,7 @@ android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginStart="24dp" - android:layout_marginTop="14dp" + android:layout_marginTop="12dp" android:layout_marginEnd="24dp" android:text="@string/require_signatures" app:layout_constraintEnd_toEndOf="parent" @@ -64,7 +64,7 @@ android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginStart="24dp" - android:layout_marginTop="10dp" + android:layout_marginTop="8dp" android:layout_marginEnd="24dp" android:max="2" android:progress="2" diff --git a/android/app/src/main/res/layout/show_master_key.xml b/android/app/src/main/res/layout/show_master_key.xml index 0068058b658d..5640e6c6ad03 100644 --- a/android/app/src/main/res/layout/show_master_key.xml +++ b/android/app/src/main/res/layout/show_master_key.xml @@ -11,6 +11,17 @@ android:layout_width="match_parent" android:layout_height="match_parent"> + + - - + app:layout_constraintTop_toTopOf="parent" + tools:text="@string/here_is" /> - + app:layout_constraintTop_toBottomOf="@+id/textView27" /> + \ No newline at end of file diff --git a/android/app/src/main/res/layout/wallet_new.xml b/android/app/src/main/res/layout/wallet_new.xml index 135eccef7cdd..dcedec8cbaf8 100644 --- a/android/app/src/main/res/layout/wallet_new.xml +++ b/android/app/src/main/res/layout/wallet_new.xml @@ -9,6 +9,25 @@ android:layout_width="match_parent" android:layout_height="wrap_content"> + + + + - - - - \ No newline at end of file From 2394c66eb61b8c3c17796978ec12ec629dd72ce9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aldin=20Kova=C4=8Devi=C4=87?= Date: Fri, 28 May 2021 19:17:04 +0200 Subject: [PATCH 073/208] Fixed capitalization on 'Add cosigner' dialog. --- .../src/main/java/org/electroncash/electroncash3/NewWallet.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/app/src/main/java/org/electroncash/electroncash3/NewWallet.kt b/android/app/src/main/java/org/electroncash/electroncash3/NewWallet.kt index 34a058b883d7..cebf4be5660d 100644 --- a/android/app/src/main/java/org/electroncash/electroncash3/NewWallet.kt +++ b/android/app/src/main/java/org/electroncash/electroncash3/NewWallet.kt @@ -415,7 +415,7 @@ class NewWalletImportMasterDialog : NewWalletDialog2() { tvPrompt.setText(keyPrompt) if (multisig && currentCosigner != 1) { - dialog.setTitle(getString(R.string.Add_Cosigner) + " $currentCosigner") + dialog.setTitle(getString(R.string.Add_cosigner) + " $currentCosigner") } dialog.getButton(AlertDialog.BUTTON_NEUTRAL).setOnClickListener { scanQR(this) } From 8d63735cc1b9fcfc58d4904bc63698729c44aab7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aldin=20Kova=C4=8Devi=C4=87?= Date: Sat, 29 May 2021 22:19:40 +0200 Subject: [PATCH 074/208] Android: Removed extra (unnecessary) whitespaces and newlines. --- .../org/electroncash/electroncash3/ColdLoad.kt | 1 - .../java/org/electroncash/electroncash3/Main.kt | 1 - .../org/electroncash/electroncash3/NewWallet.kt | 14 +++----------- 3 files changed, 3 insertions(+), 13 deletions(-) diff --git a/android/app/src/main/java/org/electroncash/electroncash3/ColdLoad.kt b/android/app/src/main/java/org/electroncash/electroncash3/ColdLoad.kt index a74830b11879..2735347cf7c8 100644 --- a/android/app/src/main/java/org/electroncash/electroncash3/ColdLoad.kt +++ b/android/app/src/main/java/org/electroncash/electroncash3/ColdLoad.kt @@ -180,7 +180,6 @@ fun canBroadcast(tx: PyObject): Boolean { * Sign a loaded transaction dialog. */ class SignPasswordDialog : PasswordDialog() { - val coldLoadDialog by lazy { targetFragment as ColdLoadDialog } val signSchnorr = daemonModel.walletType == "standard" diff --git a/android/app/src/main/java/org/electroncash/electroncash3/Main.kt b/android/app/src/main/java/org/electroncash/electroncash3/Main.kt index 729d98c65d99..33bf3ca71472 100644 --- a/android/app/src/main/java/org/electroncash/electroncash3/Main.kt +++ b/android/app/src/main/java/org/electroncash/electroncash3/Main.kt @@ -675,7 +675,6 @@ class SeedDialog : AlertDialogFragment() { class WalletInformationDialog : AlertDialogFragment() { override fun onBuildDialog(builder: AlertDialog.Builder) { - builder.setView(R.layout.wallet_information) .setPositiveButton(android.R.string.ok, null) } diff --git a/android/app/src/main/java/org/electroncash/electroncash3/NewWallet.kt b/android/app/src/main/java/org/electroncash/electroncash3/NewWallet.kt index cebf4be5660d..7908bd88a22d 100644 --- a/android/app/src/main/java/org/electroncash/electroncash3/NewWallet.kt +++ b/android/app/src/main/java/org/electroncash/electroncash3/NewWallet.kt @@ -81,7 +81,6 @@ class NewWalletDialog1 : AlertDialogFragment() { } fun closeDialogs(targetFragment: Fragment) { - if (targetFragment.targetFragment == null) { (targetFragment as DialogFragment).dismiss() return @@ -121,8 +120,6 @@ fun confirmPassword(dialog: Dialog): String { // Choose the way of generating the wallet (new seed, import seed, etc.) class KeystoreDialog : AlertDialogFragment() { - var input: String by notNull() - override fun onBuildDialog(builder: AlertDialog.Builder) { builder.setTitle(R.string.keystore) .setView(R.layout.choose_keystore) @@ -158,7 +155,6 @@ class KeystoreDialog : AlertDialogFragment() { override fun onShowDialog() { dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener { try { - val nextDialog: DialogFragment val keystoreType = spnType.selectedItemId.toInt() if (keystoreType in listOf(R.id.menuCreateSeed, R.id.menuRestoreSeed, R.id.menuCosignerSeed)) { @@ -186,9 +182,9 @@ abstract class NewWalletDialog2 : TaskLauncherDialog() { override fun onBuildDialog(builder: AlertDialog.Builder) { builder.setTitle(R.string.New_wallet) - .setView(R.layout.wallet_new_2) - .setPositiveButton(android.R.string.ok, null) - .setNegativeButton(R.string.back, null) + .setView(R.layout.wallet_new_2) + .setPositiveButton(android.R.string.ok, null) + .setNegativeButton(R.string.back, null) } override fun onPreExecute() { @@ -237,7 +233,6 @@ abstract class NewWalletDialog2 : TaskLauncherDialog() { abstract fun onCreateWallet(name: String, password: String) override fun onPostExecute(result: String) { - val multisig = arguments!!.getBoolean("multisig") /** @@ -245,7 +240,6 @@ abstract class NewWalletDialog2 : TaskLauncherDialog() { * then prompt for data for all other cosigners by calling the KeystoreDialog again. */ if (multisig) { - val currentCosigner = arguments!!.getInt("i_signer") val numCosigners = arguments!!.getInt("cosigners") @@ -538,7 +532,6 @@ class CosignerDialog : AlertDialogFragment() { dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener { try { - val nextDialog: DialogFragment nextDialog = KeystoreDialog() @@ -582,7 +575,6 @@ class MasterPublicKeyDialog : AlertDialogFragment() { dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener { try { - val nextDialog: DialogFragment nextDialog = KeystoreDialog() From f8aa0978eacee1ee101b81e884cdf1f70ac3064b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aldin=20Kova=C4=8Devi=C4=87?= Date: Sat, 29 May 2021 22:33:36 +0200 Subject: [PATCH 075/208] Android: Reusing IDs in cosigner_type menu to save line space. --- .../src/main/java/org/electroncash/electroncash3/NewWallet.kt | 4 ++-- android/app/src/main/res/menu/cosigner_type.xml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/android/app/src/main/java/org/electroncash/electroncash3/NewWallet.kt b/android/app/src/main/java/org/electroncash/electroncash3/NewWallet.kt index 7908bd88a22d..83694527342a 100644 --- a/android/app/src/main/java/org/electroncash/electroncash3/NewWallet.kt +++ b/android/app/src/main/java/org/electroncash/electroncash3/NewWallet.kt @@ -157,7 +157,7 @@ class KeystoreDialog : AlertDialogFragment() { try { val nextDialog: DialogFragment val keystoreType = spnType.selectedItemId.toInt() - if (keystoreType in listOf(R.id.menuCreateSeed, R.id.menuRestoreSeed, R.id.menuCosignerSeed)) { + if (keystoreType in listOf(R.id.menuCreateSeed, R.id.menuRestoreSeed)) { nextDialog = NewWalletSeedDialog() val seed = if (keystoreType == R.id.menuCreateSeed) daemonModel.commands.callAttr("make_seed").toString() @@ -165,7 +165,7 @@ class KeystoreDialog : AlertDialogFragment() { arguments!!.putString("seed", seed) } else if (keystoreType == R.id.menuImport) { nextDialog = NewWalletImportDialog() - } else if (keystoreType in listOf(R.id.menuImportMaster, R.id.menuCosignerKey)) { + } else if (keystoreType in listOf(R.id.menuImportMaster)) { nextDialog = NewWalletImportMasterDialog() } else { throw Exception("Unknown item: ${spnType.selectedItem}") diff --git a/android/app/src/main/res/menu/cosigner_type.xml b/android/app/src/main/res/menu/cosigner_type.xml index e22a2a700de7..82f574af6cf4 100644 --- a/android/app/src/main/res/menu/cosigner_type.xml +++ b/android/app/src/main/res/menu/cosigner_type.xml @@ -2,10 +2,10 @@ \ No newline at end of file From 6b9af66df5d357371de03b7016a6a2936eee32f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aldin=20Kova=C4=8Devi=C4=87?= Date: Sat, 29 May 2021 22:40:14 +0200 Subject: [PATCH 076/208] Android: Fixed unnecessary double call to showDialog() in NewWallet --- .../java/org/electroncash/electroncash3/NewWallet.kt | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/android/app/src/main/java/org/electroncash/electroncash3/NewWallet.kt b/android/app/src/main/java/org/electroncash/electroncash3/NewWallet.kt index 83694527342a..3914cad1c514 100644 --- a/android/app/src/main/java/org/electroncash/electroncash3/NewWallet.kt +++ b/android/app/src/main/java/org/electroncash/electroncash3/NewWallet.kt @@ -50,26 +50,23 @@ class NewWalletDialog1 : AlertDialogFragment() { val name = etName.text.toString() validateWalletName(name) val password = confirmPassword(dialog) - val nextDialog: DialogFragment val arguments = Bundle().apply { putString("name", name) putString("password", password) } - when (spnWalletType.selectedItemId.toInt()) { + val nextDialog: DialogFragment = when (spnWalletType.selectedItemId.toInt()) { R.id.menuStandardWallet -> { - nextDialog = KeystoreDialog() - showDialog(this, nextDialog.apply { setArguments(arguments) }) + KeystoreDialog() } R.id.menuMultisigWallet -> { - nextDialog = CosignerDialog() - showDialog(this, nextDialog.apply { setArguments(arguments) }) + CosignerDialog() } else -> { throw Exception("Unknown item: ${spnWalletType.selectedItem}") } } - + showDialog(this, nextDialog.apply { setArguments(arguments) }) } catch (e: ToastException) { e.show() } } } From 952abffad96d8949a40b1c971b5463a3b85444f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aldin=20Kova=C4=8Devi=C4=87?= Date: Sat, 29 May 2021 23:37:38 +0200 Subject: [PATCH 077/208] Android: Fixed 'Add cosigner' dialog title to use a new '%d of %d' string. --- .../src/main/java/org/electroncash/electroncash3/NewWallet.kt | 4 ++-- .../app/src/main/python/electroncash_gui/android/strings.py | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/android/app/src/main/java/org/electroncash/electroncash3/NewWallet.kt b/android/app/src/main/java/org/electroncash/electroncash3/NewWallet.kt index 3914cad1c514..b191541ecee6 100644 --- a/android/app/src/main/java/org/electroncash/electroncash3/NewWallet.kt +++ b/android/app/src/main/java/org/electroncash/electroncash3/NewWallet.kt @@ -134,8 +134,8 @@ class KeystoreDialog : AlertDialogFragment() { /* Handle dialog title for cosigners */ if (multisig) { - dialog.setTitle(getString(R.string.Add_cosigner) + - " (${currentCosigner} of ${numOfCosigners})") + dialog.setTitle(getString(R.string.Add_cosigner) + " " + + getString(R.string.__d_of, currentCosigner, numOfCosigners)) } val keystoreMenu: Int diff --git a/android/app/src/main/python/electroncash_gui/android/strings.py b/android/app/src/main/python/electroncash_gui/android/strings.py index 7e4fd804c157..8c6882aabb6e 100644 --- a/android/app/src/main/python/electroncash_gui/android/strings.py +++ b/android/app/src/main/python/electroncash_gui/android/strings.py @@ -11,6 +11,7 @@ from gettext import gettext as _, ngettext ngettext("%d address", "%d addresses", 1) +_("(%d of %d)") _("Are you sure you want to delete your wallet \'%s\'?") _("BIP39 seed") _("Block explorer") From 8bca0fc350a02ad82ddc25ac3ecf2aa0421054d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aldin=20Kova=C4=8Devi=C4=87?= Date: Sat, 29 May 2021 23:51:10 +0200 Subject: [PATCH 078/208] Android: Simplified closeDialogs() logic; using closeDialogs() for all dialog closings; fixed unnecessary 'nextDialog' lines. --- .../electroncash/electroncash3/NewWallet.kt | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/android/app/src/main/java/org/electroncash/electroncash3/NewWallet.kt b/android/app/src/main/java/org/electroncash/electroncash3/NewWallet.kt index b191541ecee6..81caf1fd72ac 100644 --- a/android/app/src/main/java/org/electroncash/electroncash3/NewWallet.kt +++ b/android/app/src/main/java/org/electroncash/electroncash3/NewWallet.kt @@ -78,13 +78,10 @@ class NewWalletDialog1 : AlertDialogFragment() { } fun closeDialogs(targetFragment: Fragment) { - if (targetFragment.targetFragment == null) { - (targetFragment as DialogFragment).dismiss() - return - } - - closeDialogs(targetFragment.targetFragment!!) (targetFragment as DialogFragment).dismiss() + if (targetFragment.targetFragment != null) { + closeDialogs(targetFragment.targetFragment!!) + } } fun validateWalletName(name: String) { @@ -258,8 +255,7 @@ abstract class NewWalletDialog2 : TaskLauncherDialog() { } } else { // In a standard wallet, close the dialogs and open the newly created wallet. - (targetFragment as KeystoreDialog).dismiss() - (targetFragment!!.targetFragment as NewWalletDialog1).dismiss() + closeDialogs(targetFragment!!) daemonModel.commands.callAttr("select_wallet", result) (activity as MainActivity).updateDrawer() } @@ -529,8 +525,7 @@ class CosignerDialog : AlertDialogFragment() { dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener { try { - val nextDialog: DialogFragment - nextDialog = KeystoreDialog() + val nextDialog: DialogFragment = KeystoreDialog() arguments!!.putBoolean("multisig", true) arguments!!.putInt("i_signer", 1) // current co-signer; will update @@ -572,8 +567,7 @@ class MasterPublicKeyDialog : AlertDialogFragment() { dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener { try { - val nextDialog: DialogFragment - nextDialog = KeystoreDialog() + val nextDialog: DialogFragment = KeystoreDialog() nextDialog.setArguments(arguments) showDialog(this, nextDialog) From 8361a1824bc10dbe73ef8d3ca5293fd3c5d3b60c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aldin=20Kova=C4=8Devi=C4=87?= Date: Sun, 30 May 2021 00:02:28 +0200 Subject: [PATCH 079/208] Android: Added additional clarification for create() with multisig param in console.py; updated create_multisig() to use enumerate() --- .../main/python/electroncash_gui/android/console.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/android/app/src/main/python/electroncash_gui/android/console.py b/android/app/src/main/python/electroncash_gui/android/console.py index 30d2cbce2620..dd2d0ff702a7 100644 --- a/android/app/src/main/python/electroncash_gui/android/console.py +++ b/android/app/src/main/python/electroncash_gui/android/console.py @@ -161,7 +161,10 @@ def create(self, name, password, seed=None, passphrase="", bip39_derivation=None if not multisig: storage.put('keystore', ks.dump()) wallet = Standard_Wallet(storage) - else: # for multisig, we need to get the keystore + else: + # For multisig wallets, we do not immediately create a wallet storage file. + # Instead, we just get the keystore; create_multisig() handles wallet storage + # later, once all cosigners are added. return ks wallet.update_password(None, password, encrypt=True) @@ -175,10 +178,8 @@ def create_multisig(self, name, password, keystores=None, cosigners=None, signat # Multisig wallet type storage.put("wallet_type", "%dof%d" % (signatures, cosigners)) - i = 0 - for k in keystores: - storage.put('x%d/'%(i+1), k.dump()) - i += 1 + for i, k in enumerate(keystores, start=1): + storage.put('x%d/' % i, k.dump()) storage.write() wallet = Multisig_Wallet(storage) From 88b2611d419237e9c6ddd138f958faa3c1ff403c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aldin=20Kova=C4=8Devi=C4=87?= Date: Sun, 30 May 2021 00:40:26 +0200 Subject: [PATCH 080/208] Android: Moved 'Import addresses or private keys' option to the 'wallet type' selection dialog. --- .../main/java/org/electroncash/electroncash3/NewWallet.kt | 5 +++-- android/app/src/main/res/menu/wallet_kind.xml | 3 +++ android/app/src/main/res/menu/wallet_type.xml | 3 --- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/android/app/src/main/java/org/electroncash/electroncash3/NewWallet.kt b/android/app/src/main/java/org/electroncash/electroncash3/NewWallet.kt index 81caf1fd72ac..2239546b3aa8 100644 --- a/android/app/src/main/java/org/electroncash/electroncash3/NewWallet.kt +++ b/android/app/src/main/java/org/electroncash/electroncash3/NewWallet.kt @@ -62,6 +62,9 @@ class NewWalletDialog1 : AlertDialogFragment() { R.id.menuMultisigWallet -> { CosignerDialog() } + R.id.menuImport -> { + NewWalletImportDialog() + } else -> { throw Exception("Unknown item: ${spnWalletType.selectedItem}") } @@ -157,8 +160,6 @@ class KeystoreDialog : AlertDialogFragment() { daemonModel.commands.callAttr("make_seed").toString() else null arguments!!.putString("seed", seed) - } else if (keystoreType == R.id.menuImport) { - nextDialog = NewWalletImportDialog() } else if (keystoreType in listOf(R.id.menuImportMaster)) { nextDialog = NewWalletImportMasterDialog() } else { diff --git a/android/app/src/main/res/menu/wallet_kind.xml b/android/app/src/main/res/menu/wallet_kind.xml index 4dc1b8caab02..cdf6587da714 100644 --- a/android/app/src/main/res/menu/wallet_kind.xml +++ b/android/app/src/main/res/menu/wallet_kind.xml @@ -9,4 +9,7 @@ android:id="@+id/menuMultisigWallet" android:title="@string/Multi_signature" /> + + \ No newline at end of file diff --git a/android/app/src/main/res/menu/wallet_type.xml b/android/app/src/main/res/menu/wallet_type.xml index c1866d832c82..e43941dfbeaf 100644 --- a/android/app/src/main/res/menu/wallet_type.xml +++ b/android/app/src/main/res/menu/wallet_type.xml @@ -7,9 +7,6 @@ - - From 42f2c860a01369e733c99bae5ed61d53b2ec928e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aldin=20Kova=C4=8Devi=C4=87?= Date: Sun, 30 May 2021 00:48:41 +0200 Subject: [PATCH 081/208] Android: Fixed the 'no master public key' crash on imported wallets. --- .../java/org/electroncash/electroncash3/Main.kt | 15 ++++++++++++--- .../src/main/res/layout/wallet_information.xml | 4 ++-- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/android/app/src/main/java/org/electroncash/electroncash3/Main.kt b/android/app/src/main/java/org/electroncash/electroncash3/Main.kt index 33bf3ca71472..968c1e260c4c 100644 --- a/android/app/src/main/java/org/electroncash/electroncash3/Main.kt +++ b/android/app/src/main/java/org/electroncash/electroncash3/Main.kt @@ -33,6 +33,7 @@ import kotlinx.android.synthetic.main.wallet_information.* import kotlinx.android.synthetic.main.wallet_open.* import kotlinx.android.synthetic.main.wallet_rename.* import java.io.File +import java.lang.NullPointerException import kotlin.reflect.KClass @@ -691,9 +692,17 @@ class WalletInformationDialog : AlertDialogFragment() { override fun onShowDialog() { super.onShowDialog() - val masterKey = daemonModel.commands.callAttr("getmpk").toString() - walletMasterKey.setText(masterKey) - walletMasterKey.setFocusable(false) + // Imported wallets ("imported_addr" type) do not have a master public key. + try { + val masterKey = daemonModel.commands.callAttr("getmpk").toString() + walletMasterKey.setText(masterKey) + walletMasterKey.setFocusable(false) + } catch (e: NullPointerException) { + tvMasterPublicKey.setVisibility(View.GONE) + walletMasterKey.setVisibility(View.GONE) + // Using View.INVISIBLE on the 'Copy' button to preserve layout. + (fabCopyMasterKey2 as View).setVisibility(View.INVISIBLE) + } idWalletName.setText(daemonModel.walletName) idWalletType.setText(daemonModel.walletType) diff --git a/android/app/src/main/res/layout/wallet_information.xml b/android/app/src/main/res/layout/wallet_information.xml index 150c9d43bc70..2dce493b3148 100644 --- a/android/app/src/main/res/layout/wallet_information.xml +++ b/android/app/src/main/res/layout/wallet_information.xml @@ -104,7 +104,7 @@ app:layout_constraintTop_toBottomOf="@+id/idWalletType" /> + app:layout_constraintTop_toBottomOf="@+id/tvMasterPublicKey" /> Date: Sun, 30 May 2021 00:55:51 +0200 Subject: [PATCH 082/208] Android: Removed wallet type from app title. --- .../src/main/java/org/electroncash/electroncash3/Main.kt | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/android/app/src/main/java/org/electroncash/electroncash3/Main.kt b/android/app/src/main/java/org/electroncash/electroncash3/Main.kt index 968c1e260c4c..b081d960f956 100644 --- a/android/app/src/main/java/org/electroncash/electroncash3/Main.kt +++ b/android/app/src/main/java/org/electroncash/electroncash3/Main.kt @@ -149,11 +149,7 @@ class MainActivity : AppCompatActivity(R.layout.main) { } fun onCaption(caption: Caption) { - // Get the wallet name + type - val walletName = if (daemonModel.walletType != null) { - caption.walletName + " [${daemonModel.walletType}]" - } else caption.walletName ?: app.getString(R.string.No_wallet) - + val walletName = caption.walletName ?: app.getString(R.string.No_wallet) if (resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT) { setTitle(walletName) supportActionBar!!.setSubtitle(caption.subtitle) From 0ecc3bf0ada9825e000b039daec7b658e45b74ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aldin=20Kova=C4=8Devi=C4=87?= Date: Wed, 2 Jun 2021 23:08:51 +0200 Subject: [PATCH 083/208] Android: Moved closeDialog() calls to onPostExecute(); updated the way KeystoreDialog is handled for multiple cosigners. --- .../electroncash/electroncash3/NewWallet.kt | 25 ++++++++++--------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/android/app/src/main/java/org/electroncash/electroncash3/NewWallet.kt b/android/app/src/main/java/org/electroncash/electroncash3/NewWallet.kt index 2239546b3aa8..03c37926adb1 100644 --- a/android/app/src/main/java/org/electroncash/electroncash3/NewWallet.kt +++ b/android/app/src/main/java/org/electroncash/electroncash3/NewWallet.kt @@ -19,6 +19,7 @@ import kotlinx.android.synthetic.main.multisig_cosigners.* import kotlinx.android.synthetic.main.show_master_key.* import kotlinx.android.synthetic.main.wallet_new.* import kotlinx.android.synthetic.main.wallet_new_2.* +import java.security.Key import kotlin.properties.Delegates.notNull @@ -204,10 +205,6 @@ abstract class NewWalletDialog2 : TaskLauncherDialog() { val numCosigners = arguments!!.getInt("cosigners") val numSignatures = arguments!!.getInt("signatures") - // Close the previous cosigner's keystore dialog. - // TODO: Consider a better solution for closing dialogs? - (targetFragment as KeystoreDialog).dismiss() - if (currentCosigner == numCosigners) { daemonModel.commands.callAttr( "create_multisig", name, password, @@ -216,7 +213,6 @@ abstract class NewWalletDialog2 : TaskLauncherDialog() { Kwarg("signatures", numSignatures) ) daemonModel.loadWallet(name, password) - closeDialogs(targetFragment!!) } } else { daemonModel.loadWallet(name, password) @@ -240,17 +236,22 @@ abstract class NewWalletDialog2 : TaskLauncherDialog() { if (currentCosigner < numCosigners) { // The first cosigner sees their master public key; others are prompted for data - val nextDialog: DialogFragment = if (currentCosigner == 1) { - MasterPublicKeyDialog() - } else { - KeystoreDialog() + if (currentCosigner == 1) { + (targetFragment as DialogFragment).dismiss() + val nextDialog: DialogFragment = MasterPublicKeyDialog() + nextDialog.setArguments(arguments) + showDialog(this, nextDialog) } - arguments!!.putInt("i_signer", currentCosigner + 1) + // Update dialog title for the next cosigner + val nextCosigner = currentCosigner + 1 + (targetFragment as KeystoreDialog).dialog!!.setTitle( + getString(R.string.Add_cosigner) + " " + + getString(R.string.__d_of, nextCosigner, numCosigners)) - nextDialog.setArguments(arguments) - showDialog(this, nextDialog) + arguments!!.putInt("i_signer", nextCosigner) } else { // last cosigner done; finalize wallet + closeDialogs(targetFragment!!) daemonModel.commands.callAttr("select_wallet", result) (activity as MainActivity).updateDrawer() } From 653f9cfd2859666a82dc5be54307b1dfce55a070 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aldin=20Kova=C4=8Devi=C4=87?= Date: Sat, 5 Jun 2021 13:47:35 +0200 Subject: [PATCH 084/208] Android: Removed the global keystores variable and replaced it with a dilaog argument; updated the way closeDialogs() works. --- .../electroncash/electroncash3/NewWallet.kt | 41 +++++++++++-------- .../electroncash_gui/android/console.py | 5 ++- 2 files changed, 26 insertions(+), 20 deletions(-) diff --git a/android/app/src/main/java/org/electroncash/electroncash3/NewWallet.kt b/android/app/src/main/java/org/electroncash/electroncash3/NewWallet.kt index 03c37926adb1..0f59ccae8d26 100644 --- a/android/app/src/main/java/org/electroncash/electroncash3/NewWallet.kt +++ b/android/app/src/main/java/org/electroncash/electroncash3/NewWallet.kt @@ -19,15 +19,12 @@ import kotlinx.android.synthetic.main.multisig_cosigners.* import kotlinx.android.synthetic.main.show_master_key.* import kotlinx.android.synthetic.main.wallet_new.* import kotlinx.android.synthetic.main.wallet_new_2.* -import java.security.Key import kotlin.properties.Delegates.notNull val libKeystore by lazy { libMod("keystore") } val libWallet by lazy { libMod("wallet") } -val keystores by lazy { ArrayList() } - val MAX_COSIGNERS = 15 val COSIGNER_OFFSET = 2 // min. number of multisig cosigners = 2 val SIGNATURE_OFFSET = 1 // min. number of req. multisig signatures = 1 @@ -74,17 +71,15 @@ class NewWalletDialog1 : AlertDialogFragment() { } catch (e: ToastException) { e.show() } } } - - override fun onDismiss(dialog: DialogInterface) { - super.onDismiss(dialog) - keystores.clear() - } } fun closeDialogs(targetFragment: Fragment) { - (targetFragment as DialogFragment).dismiss() - if (targetFragment.targetFragment != null) { - closeDialogs(targetFragment.targetFragment!!) + val sfm = targetFragment.activity!!.supportFragmentManager + val fragments = sfm.fragments + for (frag in fragments) { + if (frag is DialogFragment) { + frag.dismiss() + } } } @@ -204,11 +199,12 @@ abstract class NewWalletDialog2 : TaskLauncherDialog() { val currentCosigner = arguments!!.getInt("i_signer") val numCosigners = arguments!!.getInt("cosigners") val numSignatures = arguments!!.getInt("signatures") + val keystores = arguments!!.getStringArrayList("keystores") if (currentCosigner == numCosigners) { daemonModel.commands.callAttr( "create_multisig", name, password, - Kwarg("keystores", keystores.toArray()), + Kwarg("keystores", keystores!!.toArray()), Kwarg("cosigners", numCosigners), Kwarg("signatures", numSignatures) ) @@ -245,7 +241,7 @@ abstract class NewWalletDialog2 : TaskLauncherDialog() { // Update dialog title for the next cosigner val nextCosigner = currentCosigner + 1 - (targetFragment as KeystoreDialog).dialog!!.setTitle( + (targetFragment as KeystoreDialog).dialog.setTitle( getString(R.string.Add_cosigner) + " " + getString(R.string.__d_of, nextCosigner, numCosigners)) @@ -306,10 +302,9 @@ class NewWalletSeedDialog : NewWalletDialog2() { Kwarg("bip39_derivation", derivation)) if (multisig) { - keystores.add(ks) - - val masterKey = ks.callAttr("get_master_public_key").toString() + val masterKey = ks.callAttr("get", "xpub").toString() arguments!!.putString("masterKey", masterKey) + arguments!!.putStringArrayList("keystores", updateKeystores(arguments!!, ks)) } } catch (e: PyException) { @@ -431,9 +426,9 @@ class NewWalletImportMasterDialog : NewWalletDialog2() { ) if (multisig) { - val masterKey = ks.callAttr("get_master_public_key").toString() + val masterKey = ks.callAttr("get", "xpub").toString() arguments!!.putString("masterKey", masterKey) - keystores.add(ks) + arguments!!.putStringArrayList("keystores", updateKeystores(arguments!!, ks)) } } else { throw ToastException(R.string.please_specify) @@ -533,6 +528,7 @@ class CosignerDialog : AlertDialogFragment() { arguments!!.putInt("i_signer", 1) // current co-signer; will update arguments!!.putInt("cosigners", numCosigners) arguments!!.putInt("signatures", numSignatures) + arguments!!.putStringArrayList("keystores", ArrayList()) nextDialog.setArguments(arguments) showDialog(this, nextDialog) @@ -585,3 +581,12 @@ fun seedAdvice(seed: String): String { app.getString(R.string.this_seed_will) + " " + app.getString(R.string.never_disclose) } + +/** + * Update the "keystores" array list for multisig wallets. + */ +fun updateKeystores(arguments: Bundle, ks: PyObject): ArrayList { + val keystores = arguments.getStringArrayList("keystores") + keystores!!.add(ks.toString()) + return keystores +} \ No newline at end of file diff --git a/android/app/src/main/python/electroncash_gui/android/console.py b/android/app/src/main/python/electroncash_gui/android/console.py index dd2d0ff702a7..80918851dfa7 100644 --- a/android/app/src/main/python/electroncash_gui/android/console.py +++ b/android/app/src/main/python/electroncash_gui/android/console.py @@ -1,5 +1,6 @@ from __future__ import absolute_import, division, print_function +import ast from code import InteractiveConsole import os from os.path import dirname, exists, join, split @@ -165,7 +166,7 @@ def create(self, name, password, seed=None, passphrase="", bip39_derivation=None # For multisig wallets, we do not immediately create a wallet storage file. # Instead, we just get the keystore; create_multisig() handles wallet storage # later, once all cosigners are added. - return ks + return ks.dump() wallet.update_password(None, password, encrypt=True) @@ -179,7 +180,7 @@ def create_multisig(self, name, password, keystores=None, cosigners=None, signat # Multisig wallet type storage.put("wallet_type", "%dof%d" % (signatures, cosigners)) for i, k in enumerate(keystores, start=1): - storage.put('x%d/' % i, k.dump()) + storage.put('x%d/' % i, ast.literal_eval(k)) storage.write() wallet = Multisig_Wallet(storage) From 57b7644b5d728814bdaf4749f466f4a5375b9d66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aldin=20Kova=C4=8Devi=C4=87?= Date: Sat, 5 Jun 2021 23:24:15 +0200 Subject: [PATCH 085/208] Android: Removed superfluous dialog arguments ('multisig' and 'i_signer') --- .../electroncash/electroncash3/NewWallet.kt | 43 ++++++++----------- 1 file changed, 17 insertions(+), 26 deletions(-) diff --git a/android/app/src/main/java/org/electroncash/electroncash3/NewWallet.kt b/android/app/src/main/java/org/electroncash/electroncash3/NewWallet.kt index 0f59ccae8d26..1106145cdc21 100644 --- a/android/app/src/main/java/org/electroncash/electroncash3/NewWallet.kt +++ b/android/app/src/main/java/org/electroncash/electroncash3/NewWallet.kt @@ -124,18 +124,17 @@ class KeystoreDialog : AlertDialogFragment() { super.onViewCreated(view, savedInstanceState) /* Choose the appropriate keystore dropdown, based on wallet type */ - val multisig = arguments!!.getBoolean("multisig") - val currentCosigner = arguments!!.getInt("i_signer") // val numOfCosigners = arguments!!.getInt("cosigners") + val keystores = arguments!!.getStringArrayList("keystores") /* Handle dialog title for cosigners */ - if (multisig) { + if (keystores != null) { dialog.setTitle(getString(R.string.Add_cosigner) + " " + - getString(R.string.__d_of, currentCosigner, numOfCosigners)) + getString(R.string.__d_of, keystores.size + 1, numOfCosigners)) } val keystoreMenu: Int - if (multisig && currentCosigner != 1) { + if (keystores != null && keystores.size != 0) { keystoreMenu = R.menu.cosigner_type keystoreDesc.setText(R.string.add_a) } else { @@ -187,7 +186,7 @@ abstract class NewWalletDialog2 : TaskLauncherDialog() { val password = arguments!!.getString("password")!! onCreateWallet(name, password) - val multisig = arguments!!.getBoolean("multisig") + val keystores = arguments!!.getStringArrayList("keystores") /** * For multisig wallets, wait until all cosigners have been added, @@ -195,16 +194,14 @@ abstract class NewWalletDialog2 : TaskLauncherDialog() { * * Otherwise, load the created wallet. */ - if (multisig) { - val currentCosigner = arguments!!.getInt("i_signer") + if (keystores != null) { val numCosigners = arguments!!.getInt("cosigners") val numSignatures = arguments!!.getInt("signatures") - val keystores = arguments!!.getStringArrayList("keystores") - if (currentCosigner == numCosigners) { + if (keystores.size == numCosigners) { daemonModel.commands.callAttr( "create_multisig", name, password, - Kwarg("keystores", keystores!!.toArray()), + Kwarg("keystores", keystores.toArray()), Kwarg("cosigners", numCosigners), Kwarg("signatures", numSignatures) ) @@ -220,14 +217,14 @@ abstract class NewWalletDialog2 : TaskLauncherDialog() { abstract fun onCreateWallet(name: String, password: String) override fun onPostExecute(result: String) { - val multisig = arguments!!.getBoolean("multisig") + val keystores = arguments!!.getStringArrayList("keystores") /** * For multisig wallets, we need to first show the master key to the 1st cosigner, and * then prompt for data for all other cosigners by calling the KeystoreDialog again. */ - if (multisig) { - val currentCosigner = arguments!!.getInt("i_signer") + if (keystores != null) { + val currentCosigner = keystores.size val numCosigners = arguments!!.getInt("cosigners") if (currentCosigner < numCosigners) { @@ -244,8 +241,6 @@ abstract class NewWalletDialog2 : TaskLauncherDialog() { (targetFragment as KeystoreDialog).dialog.setTitle( getString(R.string.Add_cosigner) + " " + getString(R.string.__d_of, nextCosigner, numCosigners)) - - arguments!!.putInt("i_signer", nextCosigner) } else { // last cosigner done; finalize wallet closeDialogs(targetFragment!!) daemonModel.commands.callAttr("select_wallet", result) @@ -293,7 +288,7 @@ class NewWalletSeedDialog : NewWalletDialog2() { throw ToastException(R.string.Derivation_invalid) } - val multisig = arguments!!.getBoolean("multisig") + val multisig = arguments!!.containsKey("keystores") val ks = daemonModel.commands.callAttr( "create", name, password, Kwarg("seed", input), @@ -385,11 +380,9 @@ class NewWalletImportMasterDialog : NewWalletDialog2() { override fun onShowDialog() { super.onShowDialog() + val keystores = arguments!!.getStringArrayList("keystores") - val multisig = arguments!!.getBoolean("multisig") - val currentCosigner = arguments!!.getInt("i_signer") - - val keyPrompt = if (multisig && currentCosigner != 1) { + val keyPrompt = if (keystores != null && keystores.size != 0) { getString(R.string.please_enter_the_master_public_key_xpub) + " " + getString(R.string.enter_their) } else { @@ -398,8 +391,8 @@ class NewWalletImportMasterDialog : NewWalletDialog2() { } tvPrompt.setText(keyPrompt) - if (multisig && currentCosigner != 1) { - dialog.setTitle(getString(R.string.Add_cosigner) + " $currentCosigner") + if (keystores != null && keystores.size != 0) { + dialog.setTitle(getString(R.string.Add_cosigner) + " ${keystores.size + 1}") } dialog.getButton(AlertDialog.BUTTON_NEUTRAL).setOnClickListener { scanQR(this) } @@ -418,7 +411,7 @@ class NewWalletImportMasterDialog : NewWalletDialog2() { override fun onCreateWallet(name: String, password: String) { val key = input.trim() if (libKeystore.callAttr("is_bip32_key", key).toBoolean()) { - val multisig = arguments!!.getBoolean("multisig") + val multisig = arguments!!.containsKey("keystores") val ks = daemonModel.commands.callAttr( "create", name, password, Kwarg("master", key), @@ -524,8 +517,6 @@ class CosignerDialog : AlertDialogFragment() { try { val nextDialog: DialogFragment = KeystoreDialog() - arguments!!.putBoolean("multisig", true) - arguments!!.putInt("i_signer", 1) // current co-signer; will update arguments!!.putInt("cosigners", numCosigners) arguments!!.putInt("signatures", numSignatures) arguments!!.putStringArrayList("keystores", ArrayList()) From e477103232f1edc80845a58f831da828e18a0220 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aldin=20Kova=C4=8Devi=C4=87?= Date: Sat, 5 Jun 2021 23:50:09 +0200 Subject: [PATCH 086/208] Android: Moved common code up to NewWalletDialog2. --- .../electroncash/electroncash3/NewWallet.kt | 45 +++++++++---------- 1 file changed, 20 insertions(+), 25 deletions(-) diff --git a/android/app/src/main/java/org/electroncash/electroncash3/NewWallet.kt b/android/app/src/main/java/org/electroncash/electroncash3/NewWallet.kt index 1106145cdc21..82781273c56f 100644 --- a/android/app/src/main/java/org/electroncash/electroncash3/NewWallet.kt +++ b/android/app/src/main/java/org/electroncash/electroncash3/NewWallet.kt @@ -184,9 +184,16 @@ abstract class NewWalletDialog2 : TaskLauncherDialog() { override fun doInBackground(): String { val name = arguments!!.getString("name")!! val password = arguments!!.getString("password")!! - onCreateWallet(name, password) - val keystores = arguments!!.getStringArrayList("keystores") + /** + * Handle the resulting keystore. + */ + val ks = onCreateWallet(name, password) + if (ks != null) { + val masterKey = ks.callAttr("get", "xpub").toString() + arguments!!.putString("masterKey", masterKey) + arguments!!.putStringArrayList("keystores", updateKeystores(arguments!!, ks)) + } /** * For multisig wallets, wait until all cosigners have been added, @@ -194,6 +201,7 @@ abstract class NewWalletDialog2 : TaskLauncherDialog() { * * Otherwise, load the created wallet. */ + val keystores = arguments!!.getStringArrayList("keystores") if (keystores != null) { val numCosigners = arguments!!.getInt("cosigners") val numSignatures = arguments!!.getInt("signatures") @@ -214,7 +222,7 @@ abstract class NewWalletDialog2 : TaskLauncherDialog() { return name } - abstract fun onCreateWallet(name: String, password: String) + abstract fun onCreateWallet(name: String, password: String): PyObject? override fun onPostExecute(result: String) { val keystores = arguments!!.getStringArrayList("keystores") @@ -281,7 +289,7 @@ class NewWalletSeedDialog : NewWalletDialog2() { } } - override fun onCreateWallet(name: String, password: String) { + override fun onCreateWallet(name: String, password: String): PyObject? { try { if (derivation != null && !libBitcoin.callAttr("is_bip32_derivation", derivation).toBoolean()) { @@ -289,19 +297,12 @@ class NewWalletSeedDialog : NewWalletDialog2() { } val multisig = arguments!!.containsKey("keystores") - val ks = daemonModel.commands.callAttr( + return daemonModel.commands.callAttr( "create", name, password, Kwarg("seed", input), Kwarg("passphrase", passphrase), Kwarg("multisig", multisig), Kwarg("bip39_derivation", derivation)) - - if (multisig) { - val masterKey = ks.callAttr("get", "xpub").toString() - arguments!!.putString("masterKey", masterKey) - arguments!!.putStringArrayList("keystores", updateKeystores(arguments!!, ks)) - } - } catch (e: PyException) { if (e.message!!.startsWith("InvalidSeed")) { throw ToastException(R.string.the_seed_you_entered_does_not_appear) @@ -324,7 +325,7 @@ class NewWalletImportDialog : NewWalletDialog2() { dialog.getButton(AlertDialog.BUTTON_NEUTRAL).setOnClickListener { scanQR(this) } } - override fun onCreateWallet(name: String, password: String) { + override fun onCreateWallet(name: String, password: String): PyObject? { var foundAddress = false var foundPrivkey = false for (word in input.split(Regex("\\s+"))) { @@ -343,7 +344,7 @@ class NewWalletImportDialog : NewWalletDialog2() { } } - if (foundAddress) { + return if (foundAddress) { if (foundPrivkey) { throw ToastException( R.string.cannot_specify_private_keys_and_addresses_in_the_same_wallet) @@ -408,21 +409,15 @@ class NewWalletImportMasterDialog : NewWalletDialog2() { } } - override fun onCreateWallet(name: String, password: String) { + override fun onCreateWallet(name: String, password: String): PyObject? { val key = input.trim() if (libKeystore.callAttr("is_bip32_key", key).toBoolean()) { val multisig = arguments!!.containsKey("keystores") - val ks = daemonModel.commands.callAttr( - "create", name, password, - Kwarg("master", key), - Kwarg("multisig", multisig) + return daemonModel.commands.callAttr( + "create", name, password, + Kwarg("master", key), + Kwarg("multisig", multisig) ) - - if (multisig) { - val masterKey = ks.callAttr("get", "xpub").toString() - arguments!!.putString("masterKey", masterKey) - arguments!!.putStringArrayList("keystores", updateKeystores(arguments!!, ks)) - } } else { throw ToastException(R.string.please_specify) } From 0c1649402c9a898364038cb1e7fe5bf27afeac57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aldin=20Kova=C4=8Devi=C4=87?= Date: Sun, 6 Jun 2021 00:12:32 +0200 Subject: [PATCH 087/208] Android: Moved up 'new seed/key' dialog title decision to NewWalletDialog2. --- .../org/electroncash/electroncash3/NewWallet.kt | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/android/app/src/main/java/org/electroncash/electroncash3/NewWallet.kt b/android/app/src/main/java/org/electroncash/electroncash3/NewWallet.kt index 82781273c56f..0d43635598e2 100644 --- a/android/app/src/main/java/org/electroncash/electroncash3/NewWallet.kt +++ b/android/app/src/main/java/org/electroncash/electroncash3/NewWallet.kt @@ -171,10 +171,19 @@ abstract class NewWalletDialog2 : TaskLauncherDialog() { var input: String by notNull() override fun onBuildDialog(builder: AlertDialog.Builder) { - builder.setTitle(R.string.New_wallet) - .setView(R.layout.wallet_new_2) + builder.setView(R.layout.wallet_new_2) .setPositiveButton(android.R.string.ok, null) .setNegativeButton(R.string.back, null) + + // Update dialog title based on wallet type and/or current cosigner + val keystores = arguments!!.getStringArrayList("keystores") + if (keystores != null && keystores.size != 0) { + val numCosigners = arguments!!.getInt("cosigners") + builder.setTitle(getString(R.string.Add_cosigner) + " " + + getString(R.string.__d_of, keystores.size + 1, numCosigners)) + } else { + builder.setTitle(R.string.New_wallet) + } } override fun onPreExecute() { @@ -392,10 +401,6 @@ class NewWalletImportMasterDialog : NewWalletDialog2() { } tvPrompt.setText(keyPrompt) - if (keystores != null && keystores.size != 0) { - dialog.setTitle(getString(R.string.Add_cosigner) + " ${keystores.size + 1}") - } - dialog.getButton(AlertDialog.BUTTON_NEUTRAL).setOnClickListener { scanQR(this) } } From f08f34d27c2322b29a2dd0ea818e38c3b8fb4dca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aldin=20Kova=C4=8Devi=C4=87?= Date: Sun, 6 Jun 2021 01:59:08 +0200 Subject: [PATCH 088/208] Android: Updated the cosigner/signature sliders to persist across screen rotations. --- .../electroncash/electroncash3/NewWallet.kt | 32 +++++++++++++------ .../main/res/layout/multisig_cosigners.xml | 2 +- 2 files changed, 23 insertions(+), 11 deletions(-) diff --git a/android/app/src/main/java/org/electroncash/electroncash3/NewWallet.kt b/android/app/src/main/java/org/electroncash/electroncash3/NewWallet.kt index 0d43635598e2..755f7ebc545d 100644 --- a/android/app/src/main/java/org/electroncash/electroncash3/NewWallet.kt +++ b/android/app/src/main/java/org/electroncash/electroncash3/NewWallet.kt @@ -476,21 +476,30 @@ class CosignerDialog : AlertDialogFragment() { val numSignatures: Int get() = sbSignatures.progress + SIGNATURE_OFFSET + override fun onFirstShowDialog() { + super.onFirstShowDialog() + + with (sbCosigners) { + progress = 0 + } + + with (sbSignatures) { + progress = numCosigners + max = SIGNATURE_OFFSET + } + } + override fun onShowDialog() { super.onShowDialog() - - tvCosigners.text = getString(R.string.from_cosigners, 2) - tvSignatures.text = getString(R.string.require_signatures, 2) + updateUi() // Handle the total number of cosigners with (sbCosigners) { - progress = 0 max = MAX_COSIGNERS - COSIGNER_OFFSET setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener { override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) { - tvCosigners.text = getString(R.string.from_cosigners, numCosigners) - sbSignatures.max = numCosigners - 1 + updateUi() } override fun onStartTrackingTouch(seekBar: SeekBar?) {} @@ -500,12 +509,9 @@ class CosignerDialog : AlertDialogFragment() { // Handle the number of required signatures with (sbSignatures) { - progress = numCosigners - max = SIGNATURE_OFFSET - setOnSeekBarChangeListener(object: SeekBar.OnSeekBarChangeListener { override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) { - tvSignatures.text = getString(R.string.require_signatures, numSignatures) + updateUi() } override fun onStartTrackingTouch(seekBar: SeekBar?) {} @@ -528,6 +534,12 @@ class CosignerDialog : AlertDialogFragment() { } } } + + private fun updateUi() { + tvCosigners.text = getString(R.string.from_cosigners, numCosigners) + tvSignatures.text = getString(R.string.require_signatures, numSignatures) + sbSignatures.max = numCosigners - 1 + } } /** diff --git a/android/app/src/main/res/layout/multisig_cosigners.xml b/android/app/src/main/res/layout/multisig_cosigners.xml index d3096f140716..404a28a4ff31 100644 --- a/android/app/src/main/res/layout/multisig_cosigners.xml +++ b/android/app/src/main/res/layout/multisig_cosigners.xml @@ -66,7 +66,7 @@ android:layout_marginStart="24dp" android:layout_marginTop="8dp" android:layout_marginEnd="24dp" - android:max="2" + android:max="15" android:progress="2" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" From e0528802551a6255c2e1f253a6d9ffc27c93e1b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aldin=20Kova=C4=8Devi=C4=87?= Date: Sun, 6 Jun 2021 02:03:35 +0200 Subject: [PATCH 089/208] Android: Moved QR decoding in ColdLoad inside the conditional block. --- .../org/electroncash/electroncash3/ColdLoad.kt | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/android/app/src/main/java/org/electroncash/electroncash3/ColdLoad.kt b/android/app/src/main/java/org/electroncash/electroncash3/ColdLoad.kt index 2735347cf7c8..dc80198c31eb 100644 --- a/android/app/src/main/java/org/electroncash/electroncash3/ColdLoad.kt +++ b/android/app/src/main/java/org/electroncash/electroncash3/ColdLoad.kt @@ -77,14 +77,13 @@ class ColdLoadDialog : AlertDialogFragment() { override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { val result = IntentIntegrator.parseActivityResult(requestCode, resultCode, data) - // Try to decode the QR content as Base43; if that fails, treat it as is - val txHex: String = try { - baseDecode(result.contents, 43) - } catch (e: PyException) { - result.contents - } - if (result != null && result.contents != null) { + // Try to decode the QR content as Base43; if that fails, treat it as is + val txHex: String = try { + baseDecode(result.contents, 43) + } catch (e: PyException) { + result.contents + } etTransaction.setText(txHex) } else { super.onActivityResult(requestCode, resultCode, data) @@ -105,7 +104,6 @@ class ColdLoadDialog : AlertDialogFragment() { } } catch (e: ToastException) { e.show() - return } } From 26aa8e0af4f3de3fd7c5b137e27e311cb97d4f6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aldin=20Kova=C4=8Devi=C4=87?= Date: Sun, 6 Jun 2021 02:11:05 +0200 Subject: [PATCH 090/208] Android: Checking if master public key is null in WalletInformation dialog. --- .../src/main/java/org/electroncash/electroncash3/Main.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/android/app/src/main/java/org/electroncash/electroncash3/Main.kt b/android/app/src/main/java/org/electroncash/electroncash3/Main.kt index b081d960f956..805f758ce706 100644 --- a/android/app/src/main/java/org/electroncash/electroncash3/Main.kt +++ b/android/app/src/main/java/org/electroncash/electroncash3/Main.kt @@ -688,12 +688,12 @@ class WalletInformationDialog : AlertDialogFragment() { override fun onShowDialog() { super.onShowDialog() - // Imported wallets ("imported_addr" type) do not have a master public key. - try { - val masterKey = daemonModel.commands.callAttr("getmpk").toString() + // Imported wallets do not have a master public key. + val masterKey = daemonModel.commands.callAttr("getmpk")?.toString() + if (masterKey != null) { walletMasterKey.setText(masterKey) walletMasterKey.setFocusable(false) - } catch (e: NullPointerException) { + } else { tvMasterPublicKey.setVisibility(View.GONE) walletMasterKey.setVisibility(View.GONE) // Using View.INVISIBLE on the 'Copy' button to preserve layout. From 5d2d6e342d7317201e0be5f5b96718ea1de67c02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aldin=20Kova=C4=8Devi=C4=87?= Date: Sun, 6 Jun 2021 02:22:22 +0200 Subject: [PATCH 091/208] Android: Updated the logic for choosing Schnorr signatures in Send and ColdLoad; added a comment about 'keystores' in NewWallet. --- .../src/main/java/org/electroncash/electroncash3/ColdLoad.kt | 5 ++--- .../main/java/org/electroncash/electroncash3/NewWallet.kt | 3 +++ .../app/src/main/java/org/electroncash/electroncash3/Send.kt | 3 ++- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/android/app/src/main/java/org/electroncash/electroncash3/ColdLoad.kt b/android/app/src/main/java/org/electroncash/electroncash3/ColdLoad.kt index dc80198c31eb..4f544681120b 100644 --- a/android/app/src/main/java/org/electroncash/electroncash3/ColdLoad.kt +++ b/android/app/src/main/java/org/electroncash/electroncash3/ColdLoad.kt @@ -3,7 +3,6 @@ package org.electroncash.electroncash3 import android.content.ClipboardManager import android.content.Intent import android.os.Bundle -import android.view.View import android.widget.Toast import androidx.appcompat.app.AlertDialog import com.chaquo.python.Kwarg @@ -11,7 +10,6 @@ import com.chaquo.python.PyException import com.chaquo.python.PyObject import com.google.zxing.integration.android.IntentIntegrator import kotlinx.android.synthetic.main.load.* -import java.lang.IllegalArgumentException val libTransaction by lazy { libMod("transaction") } @@ -179,7 +177,8 @@ fun canBroadcast(tx: PyObject): Boolean { */ class SignPasswordDialog : PasswordDialog() { val coldLoadDialog by lazy { targetFragment as ColdLoadDialog } - val signSchnorr = daemonModel.walletType == "standard" + // Schnorr signing is supported by standard and imported private key wallets. + val signSchnorr = daemonModel.walletType in listOf("standard", "imported_privkey") val tx by lazy { libTransaction.callAttr("Transaction", arguments!!.getString("tx"), Kwarg("sign_schnorr", signSchnorr)) } diff --git a/android/app/src/main/java/org/electroncash/electroncash3/NewWallet.kt b/android/app/src/main/java/org/electroncash/electroncash3/NewWallet.kt index 755f7ebc545d..a573194c3a71 100644 --- a/android/app/src/main/java/org/electroncash/electroncash3/NewWallet.kt +++ b/android/app/src/main/java/org/electroncash/electroncash3/NewWallet.kt @@ -525,6 +525,9 @@ class CosignerDialog : AlertDialogFragment() { arguments!!.putInt("cosigners", numCosigners) arguments!!.putInt("signatures", numSignatures) + // The "keystores" argument contains keystore data for multiple cosigners + // in multisig wallets. It is used throughout the file to check if dealing + // with a multisig wallet and to get relevant cosigner data. arguments!!.putStringArrayList("keystores", ArrayList()) nextDialog.setArguments(arguments) diff --git a/android/app/src/main/java/org/electroncash/electroncash3/Send.kt b/android/app/src/main/java/org/electroncash/electroncash3/Send.kt index b2968ac8eaf6..567311a94d84 100644 --- a/android/app/src/main/java/org/electroncash/electroncash3/Send.kt +++ b/android/app/src/main/java/org/electroncash/electroncash3/Send.kt @@ -199,7 +199,8 @@ class SendDialog : TaskLauncherDialog() { val inputs = wallet.callAttr("get_spendable_coins", null, daemonModel.config, Kwarg("isInvoice", pr != null)) return try { - val signSchnorr = daemonModel.walletType == "standard" + // Schnorr signing is supported by standard and imported private key wallets. + val signSchnorr = daemonModel.walletType in listOf("standard", "imported_privkey") TxResult(wallet.callAttr("make_unsigned_transaction", inputs, outputs, daemonModel.config, Kwarg("sign_schnorr", signSchnorr)), isDummy) From 21fb70d67847a3d2b245b03d4ca0a90e23edec7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aldin=20Kova=C4=8Devi=C4=87?= Date: Sun, 6 Jun 2021 14:32:25 +0200 Subject: [PATCH 092/208] Android: Added a better check for deciding between 'Save' or 'Send' transaction dialogs in Send.kt --- .../java/org/electroncash/electroncash3/Send.kt | 13 ++++++++++++- .../org/electroncash/electroncash3/Transactions.kt | 5 +---- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/android/app/src/main/java/org/electroncash/electroncash3/Send.kt b/android/app/src/main/java/org/electroncash/electroncash3/Send.kt index 567311a94d84..f8240f8a1dcd 100644 --- a/android/app/src/main/java/org/electroncash/electroncash3/Send.kt +++ b/android/app/src/main/java/org/electroncash/electroncash3/Send.kt @@ -19,6 +19,7 @@ import kotlinx.android.synthetic.main.send.* val libPaymentRequest by lazy { libMod("paymentrequest") } +val libStorage by lazy { libMod("storage") } val MIN_FEE = 1 // sat/byte @@ -35,8 +36,18 @@ class SendDialog : TaskLauncherDialog() { } val model: Model by viewModels() + // The "unbroadcasted" flag controls whether the dialog opens as "Send" (false) or + // "Save" (true). If the dialog type has been explicitly set via an argument (e.g. from + // Main.kt), use it; otherwise, non-multisig and 1-of-n multisig wallets will open the dialog + // as "Send", and other m-of-n multisig wallets will open it as "Save". val unbroadcasted by lazy { - arguments?.getBoolean("unbroadcasted", false) ?: false + if (arguments != null && arguments!!.containsKey("unbroadcasted")) { + arguments!!.getBoolean("unbroadcasted") + } else { + val multisigType = libStorage.callAttr("multisig_type", daemonModel.walletType) + ?.toJava(IntArray::class.java) + multisigType != null && multisigType[0] != 1 + } } lateinit var amountBox: AmountBox var settingAmount = false // Prevent infinite recursion. diff --git a/android/app/src/main/java/org/electroncash/electroncash3/Transactions.kt b/android/app/src/main/java/org/electroncash/electroncash3/Transactions.kt index 6f544599a116..eb1dd87ab717 100644 --- a/android/app/src/main/java/org/electroncash/electroncash3/Transactions.kt +++ b/android/app/src/main/java/org/electroncash/electroncash3/Transactions.kt @@ -26,10 +26,7 @@ class TransactionsFragment : ListFragment(R.layout.transactions, R.id.rvTransact super.onViewCreated(view, savedInstanceState) btnSend.setOnClickListener { try { - showDialog(this, SendDialog().apply { - arguments = Bundle().apply { - putBoolean("unbroadcasted", daemonModel.walletType != "standard") - }}) + showDialog(this, SendDialog()) } catch (e: ToastException) { e.show() } } btnRequest.setOnClickListener { showDialog(this, NewRequestDialog()) } From d763755f93ae1a11ba4533e677845f9e424f66a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aldin=20Kova=C4=8Devi=C4=87?= Date: Sun, 6 Jun 2021 22:39:03 +0200 Subject: [PATCH 093/208] Android: Updated status displaying for raw hex transactions. --- .../electroncash/electroncash3/ColdLoad.kt | 45 +++++++++---------- 1 file changed, 20 insertions(+), 25 deletions(-) diff --git a/android/app/src/main/java/org/electroncash/electroncash3/ColdLoad.kt b/android/app/src/main/java/org/electroncash/electroncash3/ColdLoad.kt index 4f544681120b..2ba0fedf5aea 100644 --- a/android/app/src/main/java/org/electroncash/electroncash3/ColdLoad.kt +++ b/android/app/src/main/java/org/electroncash/electroncash3/ColdLoad.kt @@ -36,7 +36,6 @@ class ColdLoadDialog : AlertDialogFragment() { updateUI() dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener { onOK() } - dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = false dialog.getButton(AlertDialog.BUTTON_NEUTRAL).setOnClickListener { scanQR(this) } btnPaste.setOnClickListener { val clipdata = getSystemService(ClipboardManager::class).primaryClip @@ -48,26 +47,18 @@ class ColdLoadDialog : AlertDialogFragment() { } private fun updateUI() { - val currenttext = etTransaction.text - val tx: PyObject + val tx = libTransaction.callAttr("Transaction", etTransaction.text.toString()) + updateStatusText(tx) - //checks if text is blank. further validations can be added here - if (currenttext.isNotBlank()) { + // Check hex transaction signing status + if (canSign(tx)) { + dialog.getButton(AlertDialog.BUTTON_POSITIVE).setText(R.string.sign) dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = true - tx = libTransaction.callAttr("Transaction", etTransaction.text.toString()) - - updateStatusText(tx) - - // Check hex transaction signing status - if (canSign(tx)) { - dialog.getButton(AlertDialog.BUTTON_POSITIVE).setText(R.string.sign) - dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = true - } else if (canBroadcast(tx)) { - dialog.getButton(AlertDialog.BUTTON_POSITIVE).setText(R.string.send) - dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = true - } else { - dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = false - } + } else if (canBroadcast(tx)) { + dialog.getButton(AlertDialog.BUTTON_POSITIVE).setText(R.string.send) + dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = true + } else { + dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = false } } @@ -139,13 +130,17 @@ class ColdLoadDialog : AlertDialogFragment() { */ private fun updateStatusText(tx: PyObject) { try { - val txInfo = daemonModel.wallet!!.callAttr("get_tx_info", tx) - - // Check if the transaction can be processed by this wallet or not - if (txInfo["amount"] == null) { - idTxStatus.setText(R.string.transaction_unrelated) + if (etTransaction.text.isBlank()) { + idTxStatus.setText(R.string.empty) } else { - idTxStatus.setText(txInfo["status"].toString()) + // Check if the transaction can be processed by this wallet or not + val txInfo = daemonModel.wallet!!.callAttr("get_tx_info", tx) + + if (txInfo["amount"] == null) { + idTxStatus.setText(R.string.transaction_unrelated) + } else { + idTxStatus.setText(txInfo["status"].toString()) + } } } catch (e: PyException) { idTxStatus.setText(R.string.invalid) From bf71bbcf809b84a06ae3f748ac29af89cc5d4362 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aldin=20Kova=C4=8Devi=C4=87?= Date: Sun, 6 Jun 2021 23:05:25 +0200 Subject: [PATCH 094/208] Android: Enabled broadcasting of fully signed hex transactions from any wallet. --- .../src/main/java/org/electroncash/electroncash3/ColdLoad.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/app/src/main/java/org/electroncash/electroncash3/ColdLoad.kt b/android/app/src/main/java/org/electroncash/electroncash3/ColdLoad.kt index 2ba0fedf5aea..90cca1904731 100644 --- a/android/app/src/main/java/org/electroncash/electroncash3/ColdLoad.kt +++ b/android/app/src/main/java/org/electroncash/electroncash3/ColdLoad.kt @@ -136,7 +136,7 @@ class ColdLoadDialog : AlertDialogFragment() { // Check if the transaction can be processed by this wallet or not val txInfo = daemonModel.wallet!!.callAttr("get_tx_info", tx) - if (txInfo["amount"] == null) { + if (txInfo["amount"] == null && txInfo["status"].toString() != "Signed") { idTxStatus.setText(R.string.transaction_unrelated) } else { idTxStatus.setText(txInfo["status"].toString()) From d93715a4e0e2fca6e11ee3fbbea0fa3d941653bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aldin=20Kova=C4=8Devi=C4=87?= Date: Mon, 7 Jun 2021 00:30:23 +0200 Subject: [PATCH 095/208] Android: Moved UI updates when signing load hex transactions to onPostExecute. --- .../org/electroncash/electroncash3/ColdLoad.kt | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/android/app/src/main/java/org/electroncash/electroncash3/ColdLoad.kt b/android/app/src/main/java/org/electroncash/electroncash3/ColdLoad.kt index 90cca1904731..303f3d5c76a9 100644 --- a/android/app/src/main/java/org/electroncash/electroncash3/ColdLoad.kt +++ b/android/app/src/main/java/org/electroncash/electroncash3/ColdLoad.kt @@ -170,7 +170,7 @@ fun canBroadcast(tx: PyObject): Boolean { /** * Sign a loaded transaction dialog. */ -class SignPasswordDialog : PasswordDialog() { +class SignPasswordDialog : PasswordDialog() { val coldLoadDialog by lazy { targetFragment as ColdLoadDialog } // Schnorr signing is supported by standard and imported private key wallets. val signSchnorr = daemonModel.walletType in listOf("standard", "imported_privkey") @@ -179,18 +179,18 @@ class SignPasswordDialog : PasswordDialog() { Kwarg("sign_schnorr", signSchnorr)) } val wallet = daemonModel.wallet!! - override fun onPassword(password: String) { + override fun onPassword(password: String): PyObject { wallet.callAttr("sign_transaction", tx, password) - postToUiThread { - coldLoadDialog.etTransaction.setText(tx.toString()) - } + return tx } - override fun onPostExecute(result: Unit) { - if (!canBroadcast(tx)) { + override fun onPostExecute(result: PyObject) { + coldLoadDialog.etTransaction.setText(result.toString()) + + if (!canBroadcast(result)) { coldLoadDialog.dismiss() - copyToClipboard(tx.toString(), R.string.signed_transaction) + copyToClipboard(result.toString(), R.string.signed_transaction) } } } \ No newline at end of file From 33a7b283f8156992360d924d68499621060611d4 Mon Sep 17 00:00:00 2001 From: Malcolm Smith Date: Tue, 8 Jun 2021 13:31:09 +0100 Subject: [PATCH 096/208] Android: restore .mo files for strings which are generated by Python code --- android/app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index 2abfee798858..1c29e69fe032 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -64,7 +64,7 @@ android { python { srcDir REPO_ROOT include "electroncash/" - exclude "electroncash/locale/" + exclude "**/*.po", "**/*.pot" // The .mo files are used at runtime. include "electroncash_gui/__init__.py" include "electroncash_plugins/__init__.py" From 5610e65c73a17e379f570e08efecc66a3df37f4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aldin=20Kova=C4=8Devi=C4=87?= Date: Wed, 9 Jun 2021 20:37:59 +0200 Subject: [PATCH 097/208] Android: Moved signSchnorr() into a function; using canBroadcast() instead of string comparison; fixed an issue with status text wrapping. --- .../main/java/org/electroncash/electroncash3/ColdLoad.kt | 6 ++---- .../src/main/java/org/electroncash/electroncash3/Send.kt | 4 +--- .../src/main/java/org/electroncash/electroncash3/Util.kt | 8 ++++++++ android/app/src/main/res/layout/load.xml | 5 ++++- 4 files changed, 15 insertions(+), 8 deletions(-) diff --git a/android/app/src/main/java/org/electroncash/electroncash3/ColdLoad.kt b/android/app/src/main/java/org/electroncash/electroncash3/ColdLoad.kt index 303f3d5c76a9..acb7542c3457 100644 --- a/android/app/src/main/java/org/electroncash/electroncash3/ColdLoad.kt +++ b/android/app/src/main/java/org/electroncash/electroncash3/ColdLoad.kt @@ -136,7 +136,7 @@ class ColdLoadDialog : AlertDialogFragment() { // Check if the transaction can be processed by this wallet or not val txInfo = daemonModel.wallet!!.callAttr("get_tx_info", tx) - if (txInfo["amount"] == null && txInfo["status"].toString() != "Signed") { + if (txInfo["amount"] == null && !canBroadcast(tx)) { idTxStatus.setText(R.string.transaction_unrelated) } else { idTxStatus.setText(txInfo["status"].toString()) @@ -172,11 +172,9 @@ fun canBroadcast(tx: PyObject): Boolean { */ class SignPasswordDialog : PasswordDialog() { val coldLoadDialog by lazy { targetFragment as ColdLoadDialog } - // Schnorr signing is supported by standard and imported private key wallets. - val signSchnorr = daemonModel.walletType in listOf("standard", "imported_privkey") val tx by lazy { libTransaction.callAttr("Transaction", arguments!!.getString("tx"), - Kwarg("sign_schnorr", signSchnorr)) } + Kwarg("sign_schnorr", signSchnorr())) } val wallet = daemonModel.wallet!! override fun onPassword(password: String): PyObject { diff --git a/android/app/src/main/java/org/electroncash/electroncash3/Send.kt b/android/app/src/main/java/org/electroncash/electroncash3/Send.kt index f8240f8a1dcd..96cda0152f2e 100644 --- a/android/app/src/main/java/org/electroncash/electroncash3/Send.kt +++ b/android/app/src/main/java/org/electroncash/electroncash3/Send.kt @@ -210,10 +210,8 @@ class SendDialog : TaskLauncherDialog() { val inputs = wallet.callAttr("get_spendable_coins", null, daemonModel.config, Kwarg("isInvoice", pr != null)) return try { - // Schnorr signing is supported by standard and imported private key wallets. - val signSchnorr = daemonModel.walletType in listOf("standard", "imported_privkey") TxResult(wallet.callAttr("make_unsigned_transaction", inputs, outputs, - daemonModel.config, Kwarg("sign_schnorr", signSchnorr)), + daemonModel.config, Kwarg("sign_schnorr", signSchnorr())), isDummy) } catch (e: PyException) { TxResult(if (e.message!!.startsWith("NotEnoughFunds")) diff --git a/android/app/src/main/java/org/electroncash/electroncash3/Util.kt b/android/app/src/main/java/org/electroncash/electroncash3/Util.kt index 1ecf0b85c8ec..1076aeb2efc7 100644 --- a/android/app/src/main/java/org/electroncash/electroncash3/Util.kt +++ b/android/app/src/main/java/org/electroncash/electroncash3/Util.kt @@ -258,3 +258,11 @@ fun baseDecode(s: String, base: Int): String { return libBitcoin.callAttr("base_decode", s, null, base) .callAttr("hex").toString() } + +/** + * Decide whether to use Schnorr signatures. + * Schnorr signing is supported by standard and imported private key wallets. + */ +fun signSchnorr(): Boolean { + return daemonModel.walletType in listOf("standard", "imported_privkey") +} \ No newline at end of file diff --git a/android/app/src/main/res/layout/load.xml b/android/app/src/main/res/layout/load.xml index e1dabdf48795..44fee980b73a 100644 --- a/android/app/src/main/res/layout/load.xml +++ b/android/app/src/main/res/layout/load.xml @@ -71,12 +71,15 @@ From 0663281af825e50db58db8f8d74493800e97df33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aldin=20Kova=C4=8Devi=C4=87?= Date: Thu, 10 Jun 2021 22:56:52 +0200 Subject: [PATCH 098/208] Android: Showing transaction details in a dialog before signing a loaded transaction. --- .../electroncash/electroncash3/ColdLoad.kt | 36 +------ .../org/electroncash/electroncash3/Send.kt | 94 +++++++++++++++++-- 2 files changed, 92 insertions(+), 38 deletions(-) diff --git a/android/app/src/main/java/org/electroncash/electroncash3/ColdLoad.kt b/android/app/src/main/java/org/electroncash/electroncash3/ColdLoad.kt index acb7542c3457..2aae4b053cd5 100644 --- a/android/app/src/main/java/org/electroncash/electroncash3/ColdLoad.kt +++ b/android/app/src/main/java/org/electroncash/electroncash3/ColdLoad.kt @@ -100,11 +100,11 @@ class ColdLoadDialog : AlertDialogFragment() { * Sign a loaded transaction. */ private fun signLoadedTransaction() { - val dialog = SignPasswordDialog() - dialog.setArguments(Bundle().apply { - putString("tx", etTransaction.text.toString()) - }) - showDialog(this, dialog) + val arguments = Bundle().apply { + putString("txHex", etTransaction.text.toString()) + } + val dialog = SendDialog() + showDialog(this, dialog.apply { setArguments(arguments) }) } /** @@ -166,29 +166,3 @@ fun canBroadcast(tx: PyObject): Boolean { false } } - -/** - * Sign a loaded transaction dialog. - */ -class SignPasswordDialog : PasswordDialog() { - val coldLoadDialog by lazy { targetFragment as ColdLoadDialog } - - val tx by lazy { libTransaction.callAttr("Transaction", arguments!!.getString("tx"), - Kwarg("sign_schnorr", signSchnorr())) } - val wallet = daemonModel.wallet!! - - override fun onPassword(password: String): PyObject { - wallet.callAttr("sign_transaction", tx, password) - - return tx - } - - override fun onPostExecute(result: PyObject) { - coldLoadDialog.etTransaction.setText(result.toString()) - - if (!canBroadcast(result)) { - coldLoadDialog.dismiss() - copyToClipboard(result.toString(), R.string.signed_transaction) - } - } -} \ No newline at end of file diff --git a/android/app/src/main/java/org/electroncash/electroncash3/Send.kt b/android/app/src/main/java/org/electroncash/electroncash3/Send.kt index 96cda0152f2e..7ba5cc9d51f0 100644 --- a/android/app/src/main/java/org/electroncash/electroncash3/Send.kt +++ b/android/app/src/main/java/org/electroncash/electroncash3/Send.kt @@ -15,6 +15,7 @@ import com.chaquo.python.Kwarg import com.chaquo.python.PyException import com.chaquo.python.PyObject import com.google.zxing.integration.android.IntentIntegrator +import kotlinx.android.synthetic.main.load.* import kotlinx.android.synthetic.main.send.* @@ -130,6 +131,13 @@ class SendDialog : TaskLauncherDialog() { } setFeeLabel() + // Check if a transaction hex string has been passed from ColdLoad, and load it. + val txHex = arguments?.getString("txHex") + if (txHex != null) { + val tx = libTransaction.callAttr("Transaction", txHex) + setLoadedTransaction(tx) + } + dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener { onOK() } dialog.getButton(AlertDialog.BUTTON_NEUTRAL).setOnClickListener { scanQR(this) } model.tx.observe(this, Observer { onTx(it) }) @@ -150,8 +158,11 @@ class SendDialog : TaskLauncherDialog() { get() = MIN_FEE + sbFee.progress fun refreshTx() { - model.tx.refresh(TxArgs(wallet, model.paymentRequest, etAddress.text.toString(), - amountBox.amount, btnMax.isChecked)) + // If loading a transaction from ColdLoad, it does not need to be constantly refreshed. + if (arguments?.containsKey("txHex") != true) { + model.tx.refresh(TxArgs(wallet, model.paymentRequest, etAddress.text.toString(), + amountBox.amount, btnMax.isChecked)) + } } fun onTx(result: TxResult) { @@ -286,8 +297,51 @@ class SendDialog : TaskLauncherDialog() { } } + /** + * Fill in the Send dialog with data from a loaded transaction. + */ + fun setLoadedTransaction(tx: PyObject) { + dialog.setTitle(R.string.sign_the) + dialog.getButton(AlertDialog.BUTTON_POSITIVE).setText(R.string.sign) + btnContacts.setImageResource(R.drawable.ic_check_24dp) + + btnContacts.isEnabled = false + amountBox.isEditable = false + btnMax.isEnabled = false + + val txInfo = daemonModel.wallet!!.callAttr("get_tx_info", tx) + + val fee: Int = txInfo["fee"]!!.toInt() / tx.callAttr("estimated_size").toInt() + sbFee.progress = fee - 1 + sbFee.isEnabled = false + setFeeLabel(tx) + + // Get the list of transaction outputs, add every non-related address to the + // "recipients" array, and add up the total amount that is being sent. + val outputs = tx.callAttr("outputs").asList() + var amount: Long = 0 + val recipients: ArrayList = ArrayList() + for (output in outputs) { + val address = output.asList()[1] + if (!daemonModel.wallet!!.callAttr("is_mine", address).toBoolean()) { + amount += output.asList()[2].toLong() + recipients.add(address.toString()) + } + } + + // If there is only one recipient, their address will be displayed. + // Otherwise, this is a "pay to many" transaction. + if (recipients.size == 1) { + etAddress.setText(recipients[0]) + } else { + etAddress.setText(R.string.pay_to_many) + } + etAddress.isFocusable = false + setAmount(amount) + } + fun onOK() { - if (model.tx.isComplete()) { + if (arguments?.containsKey("txHex") == true || model.tx.isComplete()) { onPostExecute(Unit) } else { launchTask() @@ -300,11 +354,19 @@ class SendDialog : TaskLauncherDialog() { override fun onPostExecute(result: Unit) { try { - val txResult = model.tx.value!! - if (txResult.isDummy) throw ToastException(R.string.Invalid_address) - txResult.get() // May throw other ToastExceptions. + // If a transaction has been passed from ColdLoad, it will be used. + // Otherwise, the transaction is built from the fields in the Send dialog. + val txHex = arguments?.getString("txHex") + if (txHex == null) { + val txResult = model.tx.value!! + if (txResult.isDummy) throw ToastException(R.string.Invalid_address) + txResult.get() // May throw other ToastExceptions. + } showDialog(this, SendPasswordDialog().apply { arguments = Bundle().apply { putString("description", this@SendDialog.etDescription.text.toString()) + if (txHex != null) { + putString("txHex", txHex) + } }}) } catch (e: ToastException) { e.show() } } @@ -366,7 +428,13 @@ class SendContactsDialog : MenuDialog() { class SendPasswordDialog : PasswordDialog() { val sendDialog by lazy { targetFragment as SendDialog } - val tx by lazy { sendDialog.model.tx.value!!.get() } + val tx: PyObject by lazy { + if (arguments?.containsKey("txHex") == true) { + libTransaction.callAttr("Transaction", arguments!!.getString("txHex")) + } else { + sendDialog.model.tx.value!!.get() + } + } override fun onPassword(password: String) { val wallet = sendDialog.wallet @@ -396,6 +464,18 @@ class SendPasswordDialog : PasswordDialog() { } else { copyToClipboard(tx.toString(), R.string.signed_transaction) } + + // The presence of "txHex" argument means that this dialog had been called from ColdLoad. + // If the transaction cannot be broadcasted after signing, close the ColdLoad dialog. + // Otherwise, put the fully signed string into ColdLoad, making it available for sending. + if (arguments!!.containsKey("txHex")) { + val coldLoadDialog: ColdLoadDialog? = findDialog(activity!!, ColdLoadDialog::class) + if (!canBroadcast(tx)) { + coldLoadDialog!!.dismiss() + } else { + coldLoadDialog!!.etTransaction.setText(tx.toString()) + } + } } } From 62feea1a279bea5e95849796ad47d778d00a62ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aldin=20Kova=C4=8Devi=C4=87?= Date: Thu, 10 Jun 2021 23:04:17 +0200 Subject: [PATCH 099/208] Android: Disabled QR code scanner on loaded transactions; updated dialog title for signing of loaded transactions. --- .../app/src/main/java/org/electroncash/electroncash3/Send.kt | 3 ++- .../app/src/main/python/electroncash_gui/android/strings.py | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/android/app/src/main/java/org/electroncash/electroncash3/Send.kt b/android/app/src/main/java/org/electroncash/electroncash3/Send.kt index 7ba5cc9d51f0..c4f68c7b3991 100644 --- a/android/app/src/main/java/org/electroncash/electroncash3/Send.kt +++ b/android/app/src/main/java/org/electroncash/electroncash3/Send.kt @@ -301,8 +301,9 @@ class SendDialog : TaskLauncherDialog() { * Fill in the Send dialog with data from a loaded transaction. */ fun setLoadedTransaction(tx: PyObject) { - dialog.setTitle(R.string.sign_the) + dialog.setTitle(R.string.sign_transaction) dialog.getButton(AlertDialog.BUTTON_POSITIVE).setText(R.string.sign) + dialog.getButton(AlertDialog.BUTTON_NEUTRAL).setEnabled(false) btnContacts.setImageResource(R.drawable.ic_check_24dp) btnContacts.isEnabled = false diff --git a/android/app/src/main/python/electroncash_gui/android/strings.py b/android/app/src/main/python/electroncash_gui/android/strings.py index 8c6882aabb6e..ad380d40ce3f 100644 --- a/android/app/src/main/python/electroncash_gui/android/strings.py +++ b/android/app/src/main/python/electroncash_gui/android/strings.py @@ -49,6 +49,7 @@ _("Request") _("Restore from seed") _("Save transaction") +_("Sign transaction") _("Show seed") _("Size") _("Signed transaction") From ef287d77d45c0814155c520716f0817968f75d2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aldin=20Kova=C4=8Devi=C4=87?= Date: Fri, 11 Jun 2021 20:00:22 +0200 Subject: [PATCH 100/208] Android: Refactored the selection of a newly-created wallet into a method; updated the way default progress and max values are set for multisig sliders. --- .../electroncash/electroncash3/NewWallet.kt | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/android/app/src/main/java/org/electroncash/electroncash3/NewWallet.kt b/android/app/src/main/java/org/electroncash/electroncash3/NewWallet.kt index a573194c3a71..290e9e776cca 100644 --- a/android/app/src/main/java/org/electroncash/electroncash3/NewWallet.kt +++ b/android/app/src/main/java/org/electroncash/electroncash3/NewWallet.kt @@ -259,17 +259,19 @@ abstract class NewWalletDialog2 : TaskLauncherDialog() { getString(R.string.Add_cosigner) + " " + getString(R.string.__d_of, nextCosigner, numCosigners)) } else { // last cosigner done; finalize wallet - closeDialogs(targetFragment!!) - daemonModel.commands.callAttr("select_wallet", result) - (activity as MainActivity).updateDrawer() + selectWallet(targetFragment!!, result) } } else { // In a standard wallet, close the dialogs and open the newly created wallet. - closeDialogs(targetFragment!!) - daemonModel.commands.callAttr("select_wallet", result) - (activity as MainActivity).updateDrawer() + selectWallet(targetFragment!!, result) } } + + private fun selectWallet(targetFragment: Fragment, name: String) { + closeDialogs(targetFragment) + daemonModel.commands.callAttr("select_wallet", name) + (activity as MainActivity).updateDrawer() + } } @@ -484,8 +486,8 @@ class CosignerDialog : AlertDialogFragment() { } with (sbSignatures) { - progress = numCosigners - max = SIGNATURE_OFFSET + progress = numCosigners - SIGNATURE_OFFSET + max = numCosigners - SIGNATURE_OFFSET } } @@ -541,7 +543,7 @@ class CosignerDialog : AlertDialogFragment() { private fun updateUi() { tvCosigners.text = getString(R.string.from_cosigners, numCosigners) tvSignatures.text = getString(R.string.require_signatures, numSignatures) - sbSignatures.max = numCosigners - 1 + sbSignatures.max = numCosigners - SIGNATURE_OFFSET } } From 6a6c146d3a29e973134d7b2c1299526b657bbdf7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aldin=20Kova=C4=8Devi=C4=87?= Date: Sun, 13 Jun 2021 17:29:42 +0200 Subject: [PATCH 101/208] Android: Updated NewWallet.kt to not share arguments between dialogs; moved risky argument modification from doInBackground() to onPostExecute() --- .../electroncash/electroncash3/NewWallet.kt | 87 +++++++++++-------- 1 file changed, 50 insertions(+), 37 deletions(-) diff --git a/android/app/src/main/java/org/electroncash/electroncash3/NewWallet.kt b/android/app/src/main/java/org/electroncash/electroncash3/NewWallet.kt index 290e9e776cca..47d0532fdcab 100644 --- a/android/app/src/main/java/org/electroncash/electroncash3/NewWallet.kt +++ b/android/app/src/main/java/org/electroncash/electroncash3/NewWallet.kt @@ -129,8 +129,7 @@ class KeystoreDialog : AlertDialogFragment() { /* Handle dialog title for cosigners */ if (keystores != null) { - dialog.setTitle(getString(R.string.Add_cosigner) + " " + - getString(R.string.__d_of, keystores.size + 1, numOfCosigners)) + setMultsigTitle(keystores.size + 1, numOfCosigners) } val keystoreMenu: Int @@ -148,26 +147,32 @@ class KeystoreDialog : AlertDialogFragment() { dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener { try { val nextDialog: DialogFragment + val nextArguments = Bundle(arguments) val keystoreType = spnType.selectedItemId.toInt() if (keystoreType in listOf(R.id.menuCreateSeed, R.id.menuRestoreSeed)) { nextDialog = NewWalletSeedDialog() val seed = if (keystoreType == R.id.menuCreateSeed) daemonModel.commands.callAttr("make_seed").toString() else null - arguments!!.putString("seed", seed) + nextArguments.putString("seed", seed) } else if (keystoreType in listOf(R.id.menuImportMaster)) { nextDialog = NewWalletImportMasterDialog() } else { throw Exception("Unknown item: ${spnType.selectedItem}") } - nextDialog.setArguments(arguments) + nextDialog.setArguments(nextArguments) showDialog(this, nextDialog) } catch (e: ToastException) { e.show() } } } + + fun setMultsigTitle(m: Int, n: Int) { + dialog.setTitle(getString(R.string.Add_cosigner) + " " + + getString(R.string.__d_of, m, n)) + } } -abstract class NewWalletDialog2 : TaskLauncherDialog() { +abstract class NewWalletDialog2 : TaskLauncherDialog() { var input: String by notNull() override fun onBuildDialog(builder: AlertDialog.Builder) { @@ -190,19 +195,10 @@ abstract class NewWalletDialog2 : TaskLauncherDialog() { input = etInput.text.toString() } - override fun doInBackground(): String { + override fun doInBackground(): PyObject? { val name = arguments!!.getString("name")!! val password = arguments!!.getString("password")!! - - /** - * Handle the resulting keystore. - */ val ks = onCreateWallet(name, password) - if (ks != null) { - val masterKey = ks.callAttr("get", "xpub").toString() - arguments!!.putString("masterKey", masterKey) - arguments!!.putStringArrayList("keystores", updateKeystores(arguments!!, ks)) - } /** * For multisig wallets, wait until all cosigners have been added, @@ -210,7 +206,7 @@ abstract class NewWalletDialog2 : TaskLauncherDialog() { * * Otherwise, load the created wallet. */ - val keystores = arguments!!.getStringArrayList("keystores") + val keystores = updatedKeystores(arguments!!, ks) if (keystores != null) { val numCosigners = arguments!!.getInt("cosigners") val numSignatures = arguments!!.getInt("signatures") @@ -228,13 +224,14 @@ abstract class NewWalletDialog2 : TaskLauncherDialog() { daemonModel.loadWallet(name, password) } - return name + return ks } abstract fun onCreateWallet(name: String, password: String): PyObject? - override fun onPostExecute(result: String) { - val keystores = arguments!!.getStringArrayList("keystores") + override fun onPostExecute(result: PyObject?) { + val keystores = updatedKeystores(arguments!!, result) + val name = arguments!!.getString("name") /** * For multisig wallets, we need to first show the master key to the 1st cosigner, and @@ -245,29 +242,35 @@ abstract class NewWalletDialog2 : TaskLauncherDialog() { val numCosigners = arguments!!.getInt("cosigners") if (currentCosigner < numCosigners) { + val nextArguments = Bundle(arguments) // The first cosigner sees their master public key; others are prompted for data if (currentCosigner == 1) { (targetFragment as DialogFragment).dismiss() val nextDialog: DialogFragment = MasterPublicKeyDialog() - nextDialog.setArguments(arguments) + nextDialog.setArguments(nextArguments.apply { + val masterKey = result!!.callAttr("get", "xpub").toString() + putString("masterKey", masterKey) + }) showDialog(this, nextDialog) } - // Update dialog title for the next cosigner + // Update dialog title and arguments for the next cosigner val nextCosigner = currentCosigner + 1 - (targetFragment as KeystoreDialog).dialog.setTitle( - getString(R.string.Add_cosigner) + " " + - getString(R.string.__d_of, nextCosigner, numCosigners)) + val keystoreDialog = (targetFragment as KeystoreDialog) + keystoreDialog.setArguments(nextArguments.apply { + putStringArrayList("keystores", keystores) + }) + keystoreDialog.setMultsigTitle(nextCosigner, numCosigners) } else { // last cosigner done; finalize wallet - selectWallet(targetFragment!!, result) + selectWallet(targetFragment!!, name) } } else { // In a standard wallet, close the dialogs and open the newly created wallet. - selectWallet(targetFragment!!, result) + selectWallet(targetFragment!!, name) } } - private fun selectWallet(targetFragment: Fragment, name: String) { + private fun selectWallet(targetFragment: Fragment, name: String?) { closeDialogs(targetFragment) daemonModel.commands.callAttr("select_wallet", name) (activity as MainActivity).updateDrawer() @@ -524,15 +527,15 @@ class CosignerDialog : AlertDialogFragment() { dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener { try { val nextDialog: DialogFragment = KeystoreDialog() - - arguments!!.putInt("cosigners", numCosigners) - arguments!!.putInt("signatures", numSignatures) + val nextArguments = Bundle(arguments) + nextArguments.putInt("cosigners", numCosigners) + nextArguments.putInt("signatures", numSignatures) // The "keystores" argument contains keystore data for multiple cosigners // in multisig wallets. It is used throughout the file to check if dealing // with a multisig wallet and to get relevant cosigner data. - arguments!!.putStringArrayList("keystores", ArrayList()) + nextArguments.putStringArrayList("keystores", ArrayList()) - nextDialog.setArguments(arguments) + nextDialog.setArguments(nextArguments) showDialog(this, nextDialog) } catch (e: ToastException) { e.show() @@ -574,8 +577,9 @@ class MasterPublicKeyDialog : AlertDialogFragment() { dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener { try { val nextDialog: DialogFragment = KeystoreDialog() + val nextArguments = Bundle(arguments) - nextDialog.setArguments(arguments) + nextDialog.setArguments(nextArguments) showDialog(this, nextDialog) } catch (e: ToastException) { e.show() @@ -591,10 +595,19 @@ fun seedAdvice(seed: String): String { } /** - * Update the "keystores" array list for multisig wallets. + * Returns the updated "keystores" array list for multisig wallets, used to check whether to + * finalize multisig wallet creation (or if it is a multisig wallet at all). + * In intermediary steps (adding non-final cosigners), the updated keystores will be stored into + * a dialog argument in onPostExecute(). */ -fun updateKeystores(arguments: Bundle, ks: PyObject): ArrayList { +fun updatedKeystores(arguments: Bundle, ks: PyObject?): ArrayList? { val keystores = arguments.getStringArrayList("keystores") - keystores!!.add(ks.toString()) - return keystores + if (keystores != null) { + val newKeystores = ArrayList(keystores) + if (ks != null) { + newKeystores.add(ks.toString()) + } + return newKeystores + } + return null } \ No newline at end of file From 895c2d8275dd30f7e26a7c1614d5c0bf3f4eb77c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aldin=20Kova=C4=8Devi=C4=87?= Date: Sun, 13 Jun 2021 20:45:46 +0200 Subject: [PATCH 102/208] Android: Added the displaying of all master public keys in multisig wallets. --- .../org/electroncash/electroncash3/Main.kt | 56 +++++++++++++++---- .../electroncash/electroncash3/NewWallet.kt | 2 +- .../electroncash_gui/android/strings.py | 2 + .../src/main/res/layout/show_master_key.xml | 2 +- .../main/res/layout/wallet_information.xml | 9 ++- 5 files changed, 57 insertions(+), 14 deletions(-) diff --git a/android/app/src/main/java/org/electroncash/electroncash3/Main.kt b/android/app/src/main/java/org/electroncash/electroncash3/Main.kt index 805f758ce706..ed89380cf611 100644 --- a/android/app/src/main/java/org/electroncash/electroncash3/Main.kt +++ b/android/app/src/main/java/org/electroncash/electroncash3/Main.kt @@ -17,6 +17,7 @@ import android.view.MenuItem import android.view.View import android.view.ViewGroup import android.view.WindowManager +import android.widget.RadioButton import android.widget.TextView import android.widget.Toast import androidx.appcompat.app.AlertDialog @@ -33,7 +34,6 @@ import kotlinx.android.synthetic.main.wallet_information.* import kotlinx.android.synthetic.main.wallet_open.* import kotlinx.android.synthetic.main.wallet_rename.* import java.io.File -import java.lang.NullPointerException import kotlin.reflect.KClass @@ -234,7 +234,8 @@ class MainActivity : AppCompatActivity(R.layout.main) { } R.id.menuChangePassword -> showDialog(this, PasswordChangeDialog()) R.id.menuShowSeed -> { showDialog(this, SeedPasswordDialog()) } - R.id.menuWalletInformation -> { showDialog(this, WalletInformationDialog()) } + R.id.menuWalletInformation -> { showDialog(this, + WalletInformationDialog().apply { arguments = Bundle() }) } R.id.menuExportSigned -> { try { showDialog(this, SendDialog().apply { @@ -681,27 +682,60 @@ class WalletInformationDialog : AlertDialogFragment() { fabCopyMasterKey2.setOnClickListener { val textToCopy = walletMasterKey.text - copyToClipboard(textToCopy, R.string.Master_public) + copyToClipboard(textToCopy, R.string.Master_public_key) } } override fun onShowDialog() { super.onShowDialog() - // Imported wallets do not have a master public key. - val masterKey = daemonModel.commands.callAttr("getmpk")?.toString() - if (masterKey != null) { - walletMasterKey.setText(masterKey) + displayMasterPublicKeys() + idWalletName.setText(daemonModel.walletName) + idWalletType.setText(daemonModel.walletType) + idScriptType.setText(daemonModel.scriptType) + } + + private fun displayMasterPublicKeys() { + val mpks = daemonModel.wallet!!.callAttr("get_master_public_keys")?.asList() + + if (mpks != null && mpks.size != 0) { walletMasterKey.setFocusable(false) + // For multisig wallets, display a radio group with selectable cosigners. + if (mpks.size > 1) { + rgCosigners.setVisibility(View.VISIBLE) + tvMasterPublicKey.setText(R.string.Master_public_keys) + + for ((i, mpk) in mpks.withIndex()) { + val rb = RadioButton(dialog.context) + rb.setText(getString(R.string.cosigner__d, i + 1)) + rb.setOnClickListener { + walletMasterKey.setText(mpk.toString()) + arguments?.putInt("selected", i) + } + rgCosigners.addView(rb) + // Set the first cosigner as selected. + if (arguments?.getInt("selected") == null && i == 0) { + walletMasterKey.setText(mpk.toString()) + rgCosigners.check(rb.id) + } + } + + // Preserve the selected cosigner across rotations + val selected = arguments?.getInt("selected") + if (selected != null) { + rgCosigners.getChildAt(selected).performClick() + } + } else { + // For a single wallet, display the single master public key. + walletMasterKey.setText(mpks[0].toString()) + rgCosigners.setVisibility(View.GONE) + } } else { + // Imported wallets do not have a master public key. tvMasterPublicKey.setVisibility(View.GONE) walletMasterKey.setVisibility(View.GONE) // Using View.INVISIBLE on the 'Copy' button to preserve layout. (fabCopyMasterKey2 as View).setVisibility(View.INVISIBLE) } - - idWalletName.setText(daemonModel.walletName) - idWalletType.setText(daemonModel.walletType) - idScriptType.setText(daemonModel.scriptType) } } diff --git a/android/app/src/main/java/org/electroncash/electroncash3/NewWallet.kt b/android/app/src/main/java/org/electroncash/electroncash3/NewWallet.kt index 47d0532fdcab..2cfe6f45753f 100644 --- a/android/app/src/main/java/org/electroncash/electroncash3/NewWallet.kt +++ b/android/app/src/main/java/org/electroncash/electroncash3/NewWallet.kt @@ -564,7 +564,7 @@ class MasterPublicKeyDialog : AlertDialogFragment() { super.onViewCreated(view, savedInstanceState) fabCopyMasterKey.setOnClickListener { - copyToClipboard(walletMasterKey.text, R.string.Master_public) + copyToClipboard(walletMasterKey.text, R.string.Master_public_key) } } diff --git a/android/app/src/main/python/electroncash_gui/android/strings.py b/android/app/src/main/python/electroncash_gui/android/strings.py index ad380d40ce3f..b6220ae4c728 100644 --- a/android/app/src/main/python/electroncash_gui/android/strings.py +++ b/android/app/src/main/python/electroncash_gui/android/strings.py @@ -23,6 +23,7 @@ _("Console") _("Copyright © 2017-2021 Electron Cash LLC and the Electron Cash developers.") _("Current password") +_("Cosigner %d") _("Delete wallet") _("Derivation invalid") _("Disconnect") @@ -38,6 +39,7 @@ _("Load transaction") _("Made with Chaquopy, the Python SDK for Android.") _("Master public key") +_("Master public keys") _("New password") _("New wallet") _("No wallet is open.") diff --git a/android/app/src/main/res/layout/show_master_key.xml b/android/app/src/main/res/layout/show_master_key.xml index 5640e6c6ad03..2e464ac65dfb 100644 --- a/android/app/src/main/res/layout/show_master_key.xml +++ b/android/app/src/main/res/layout/show_master_key.xml @@ -17,7 +17,7 @@ android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginStart="24dp" - android:text="@string/Master_public" + android:text="@string/Master_public_key" app:layout_constraintEnd_toStartOf="@+id/fabCopyMasterKey" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> diff --git a/android/app/src/main/res/layout/wallet_information.xml b/android/app/src/main/res/layout/wallet_information.xml index 2dce493b3148..017d90faa056 100644 --- a/android/app/src/main/res/layout/wallet_information.xml +++ b/android/app/src/main/res/layout/wallet_information.xml @@ -113,6 +113,13 @@ app:layout_constraintTop_toBottomOf="@+id/idScriptType" tools:text="@string/here_is" /> + + + app:layout_constraintTop_toBottomOf="@+id/rgCosigners" /> Date: Thu, 17 Jun 2021 18:01:46 +0200 Subject: [PATCH 103/208] Look for external plugins in ELECTRON_CASH_PATH It was previously only looking at {user_dir}/external_plugins and creating that directory. Fixes #2300 --- electroncash/plugins.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/electroncash/plugins.py b/electroncash/plugins.py index ef4d2e4bba7a..8bbc805a9088 100644 --- a/electroncash/plugins.py +++ b/electroncash/plugins.py @@ -41,7 +41,7 @@ from . import bitcoin from . import version from .i18n import _ -from .util import (print_error, print_stderr, make_dir, profiler, user_dir, +from .util import (print_error, print_stderr, make_dir, profiler, DaemonThread, PrintError, ThreadJob, UserCancelled) plugin_loaders = {} @@ -317,14 +317,9 @@ def is_external_plugin_available(self, name, w): return self.is_plugin_available(d, w) def get_external_plugin_dir(self): - # It's possible the plugins are being stored in a local directory - # and the rest of the data is being stored in the non-local directory. - local_user_dir = user_dir(prefer_local=True) - # Environment does not have a user directory (will be unit tests where there are no external plugins). - if local_user_dir is None: + if self.config.path is None: return None - make_dir(local_user_dir) - external_plugin_dir = os.path.join(local_user_dir, "external_plugins") + external_plugin_dir = os.path.join(self.config.path, "external_plugins") make_dir(external_plugin_dir) return external_plugin_dir From 0cd9a808523394ca754360f5f0186793f4f686d1 Mon Sep 17 00:00:00 2001 From: MrNaif2018 <39452697+MrNaif2018@users.noreply.github.com> Date: Wed, 30 Jun 2021 15:29:20 +0300 Subject: [PATCH 104/208] Support disabling JSON-RPC server in Daemon (#2305) * Support disabling JSON-RPC server in Daemon * Update electroncash/daemon.py Co-authored-by: Calin Culianu --- electroncash/daemon.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/electroncash/daemon.py b/electroncash/daemon.py index 9cb7ac75b0b0..4bc74ba3996c 100644 --- a/electroncash/daemon.py +++ b/electroncash/daemon.py @@ -138,7 +138,7 @@ def get_rpc_credentials(config): class Daemon(DaemonThread): - def __init__(self, config, fd, is_gui, plugins): + def __init__(self, config, fd, is_gui, plugins, *, listen_jsonrpc=True): DaemonThread.__init__(self) self.plugins = plugins self.config = config @@ -156,8 +156,9 @@ def __init__(self, config, fd, is_gui, plugins): self.network.add_jobs([self.fx]) self.gui = None self.wallets = {} - # Setup JSONRPC server - self.init_server(config, fd, is_gui) + if listen_jsonrpc: + # Setup JSONRPC server + self.init_server(config, fd, is_gui) def init_server(self, config, fd, is_gui): host = config.get('rpchost', '127.0.0.1') From 531b230cd7c9a77574edb1a6d99e4c99bf78405c Mon Sep 17 00:00:00 2001 From: MrNaif2018 <39452697+MrNaif2018@users.noreply.github.com> Date: Sat, 3 Jul 2021 20:59:08 +0300 Subject: [PATCH 105/208] Add feerate argument to payto/paytomany (#2306) This pull request adds feerate parameter to CLI functions payto and paytomany. It allows setting feerate for a transaction without for example calling payto once, getting tx size (via a function that is not exposed by default commands.py) and then calling payto once again with calculated fee. --- electroncash/commands.py | 23 ++++++++++++++++------- electroncash/simple_config.py | 7 ++++++- electroncash/wallet.py | 2 ++ 3 files changed, 24 insertions(+), 8 deletions(-) diff --git a/electroncash/commands.py b/electroncash/commands.py index 99c2202634ae..146a58bccd34 100644 --- a/electroncash/commands.py +++ b/electroncash/commands.py @@ -45,6 +45,7 @@ from .transaction import Transaction, multisig_script, OPReturn from .util import bfh, bh2u, format_satoshis, json_decode, print_error, to_bytes from .paymentrequest import PR_PAID, PR_UNPAID, PR_UNKNOWN, PR_EXPIRED +from .simple_config import SimpleConfig known_commands = {} @@ -509,8 +510,10 @@ def verifymessage(self, address, signature, message): message = util.to_bytes(message) return bitcoin.verify_message(address, sig, message) - def _mktx(self, outputs, fee=None, change_addr=None, domain=None, nocheck=False, + def _mktx(self, outputs, fee=None, feerate=None, change_addr=None, domain=None, nocheck=False, unsigned=False, password=None, locktime=None, op_return=None, op_return_raw=None, addtransaction=False): + if fee is not None and feerate is not None: + raise ValueError("Cannot specify both 'fee' and 'feerate' at the same time!") if op_return and op_return_raw: raise ValueError('Both op_return and op_return_raw cannot be specified together!') self.nocheck = nocheck @@ -534,7 +537,12 @@ def _mktx(self, outputs, fee=None, change_addr=None, domain=None, nocheck=False, final_outputs.append((TYPE_ADDRESS, address, amount)) coins = self.wallet.get_spendable_coins(domain, self.config) - tx = self.wallet.make_unsigned_transaction(coins, final_outputs, self.config, fee, change_addr) + if feerate is not None: + fee_per_kb = 1000 * PyDecimal(feerate) + fee_estimator = lambda size: SimpleConfig.estimate_fee_for_feerate(fee_per_kb, size) + else: + fee_estimator = fee + tx = self.wallet.make_unsigned_transaction(coins, final_outputs, self.config, fee_estimator, change_addr) if locktime != None: tx.locktime = locktime if not unsigned: @@ -547,20 +555,20 @@ def _mktx(self, outputs, fee=None, change_addr=None, domain=None, nocheck=False, return tx @command('wp') - def payto(self, destination, amount, fee=None, from_addr=None, change_addr=None, nocheck=False, unsigned=False, password=None, locktime=None, + def payto(self, destination, amount, fee=None, feerate=None, from_addr=None, change_addr=None, nocheck=False, unsigned=False, password=None, locktime=None, op_return=None, op_return_raw=None, addtransaction=False): """Create a transaction. """ tx_fee = satoshis(fee) domain = from_addr.split(',') if from_addr else None - tx = self._mktx([(destination, amount)], tx_fee, change_addr, domain, nocheck, unsigned, password, locktime, op_return, op_return_raw, addtransaction=addtransaction) + tx = self._mktx([(destination, amount)], tx_fee, feerate, change_addr, domain, nocheck, unsigned, password, locktime, op_return, op_return_raw, addtransaction=addtransaction) return tx.as_dict() @command('wp') - def paytomany(self, outputs, fee=None, from_addr=None, change_addr=None, nocheck=False, unsigned=False, password=None, locktime=None, addtransaction=False): + def paytomany(self, outputs, fee=None, feerate=None, from_addr=None, change_addr=None, nocheck=False, unsigned=False, password=None, locktime=None, addtransaction=False): """Create a multi-output transaction. """ tx_fee = satoshis(fee) domain = from_addr.split(',') if from_addr else None - tx = self._mktx(outputs, tx_fee, change_addr, domain, nocheck, unsigned, password, locktime, addtransaction=addtransaction) + tx = self._mktx(outputs, tx_fee, feerate, change_addr, domain, nocheck, unsigned, password, locktime, addtransaction=addtransaction) return tx.as_dict() @command('w') @@ -857,7 +865,8 @@ def help(self): 'entropy': (None, "Custom entropy"), 'expiration': (None, "Time in seconds"), 'expired': (None, "Show only expired requests."), - 'fee': ("-f", "Transaction fee (in BCH)"), + 'fee': ("-f", "Transaction fee (absolute, in BCH)"), + 'feerate': (None, "Transaction fee rate (in sat/byte)"), 'force': (None, "Create new address beyond gap limit, if no more addresses are available."), 'from_addr': ("-F", "Source address (must be a wallet address; use sweep to spend from non-wallet address)."), 'frozen': (None, "Show only frozen addresses"), diff --git a/electroncash/simple_config.py b/electroncash/simple_config.py index 8f765f885c06..e612beace210 100644 --- a/electroncash/simple_config.py +++ b/electroncash/simple_config.py @@ -4,6 +4,7 @@ import time import os import stat +from decimal import Decimal as PyDecimal from . import util from copy import deepcopy @@ -351,7 +352,11 @@ def has_custom_fee_rate(self): return i >= 0 def estimate_fee(self, size): - return int(self.fee_per_kb() * size / 1000.) + return self.estimate_fee_for_feerate(self.fee_per_kb(), size) + + @classmethod + def estimate_fee_for_feerate(cls, fee_per_kb, size): + return int(PyDecimal(fee_per_kb) * PyDecimal(size) / 1000) def update_fee_estimates(self, key, value): self.fee_estimates[key] = value diff --git a/electroncash/wallet.py b/electroncash/wallet.py index 583f808f6e16..9c4d89595c0c 100644 --- a/electroncash/wallet.py +++ b/electroncash/wallet.py @@ -1901,6 +1901,8 @@ def make_unsigned_transaction(self, inputs, outputs, config, fixed_fee=None, cha # Fee estimator if fixed_fee is None: fee_estimator = config.estimate_fee + elif callable(fixed_fee): + fee_estimator = fixed_fee else: fee_estimator = lambda size: fixed_fee From 93db2b07674a16997b358409622524272f4a2202 Mon Sep 17 00:00:00 2001 From: Malcolm Smith Date: Mon, 5 Jul 2021 20:28:55 +0100 Subject: [PATCH 106/208] Android: update README --- android/README.md | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/android/README.md b/android/README.md index cac677a019a8..5ac9b355c4ab 100644 --- a/android/README.md +++ b/android/README.md @@ -1,19 +1,19 @@ # Electron Cash Android app -## Development - To start developing the app, just open this directory in Android Studio. -### Requirements + +## Requirements You'll need to set up the following things before building the app: -* Python must be on the PATH under the name `python3` on Linux/Mac, or `py` on Windows, and it - must have the packages listed in `build-requirements.txt`. +* Python 3.8 must be on the PATH under the name `python3.8` or `python3` on Linux/Mac, or `py` + on Windows, and it must have the packages listed in `build-requirements.txt`. * The commands `xgettext` and `msgfmt` must be on the PATH. On Windows, the easiest way to get these is to install MSYS2. -### Strings + +## Strings Most user interface strings are reused from the desktop and iOS apps. Android-specific strings should be added to `app/src/main/python/electroncash_gui/android/strings.py`. @@ -28,9 +28,10 @@ The Android string IDs are generated from the first 2 words of each string, plus words as necessary to make them unique. So if any of the source strings change, you may need to update ID references in the code. + ## Release -For public releases, the following reproducible build process can be run on Linux x86-64: +For public releases, the following reproducible build process should be run on Linux x86-64: If necessary, install Docker using the [instructions on its website](https://docs.docker.com/install/#supported-platforms). From 8e376b2d37646ff00c54a990487c1643480f8901 Mon Sep 17 00:00:00 2001 From: Malcolm Smith Date: Tue, 6 Jul 2021 22:03:23 +0100 Subject: [PATCH 107/208] Android: catch overflow errors in AmountBox (closes #2288) --- .../main/java/org/electroncash/electroncash3/Exchange.kt | 4 ++++ .../src/main/java/org/electroncash/electroncash3/Send.kt | 9 ++++++++- .../src/main/java/org/electroncash/electroncash3/Util.kt | 7 ++++++- 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/android/app/src/main/java/org/electroncash/electroncash3/Exchange.kt b/android/app/src/main/java/org/electroncash/electroncash3/Exchange.kt index 74b8872252ce..05535cd495ea 100644 --- a/android/app/src/main/java/org/electroncash/electroncash3/Exchange.kt +++ b/android/app/src/main/java/org/electroncash/electroncash3/Exchange.kt @@ -1,6 +1,8 @@ package org.electroncash.electroncash3 +import android.widget.Toast import com.chaquo.python.Kwarg +import com.chaquo.python.PyException // on_history is not included, because that's also generated by wallet updates. val EXCHANGE_CALLBACKS = setOf("on_quotes") @@ -36,6 +38,8 @@ fun fiatToSatoshis(s: String): Long? { return fx.callAttr("fiat_to_amount", s.toDouble())?.toLong() } catch (e: NumberFormatException) { throw ToastException(R.string.Invalid_amount) + } catch (e: PyException) { + throw if (e.message!!.startsWith("OverflowError")) ToastException(e) else e } } diff --git a/android/app/src/main/java/org/electroncash/electroncash3/Send.kt b/android/app/src/main/java/org/electroncash/electroncash3/Send.kt index c4f68c7b3991..68d214c752f5 100644 --- a/android/app/src/main/java/org/electroncash/electroncash3/Send.kt +++ b/android/app/src/main/java/org/electroncash/electroncash3/Send.kt @@ -262,7 +262,14 @@ class SendDialog : TaskLauncherDialog() { setPaymentRequest(null) parsed.callAttr("get", "address")?.let { etAddress.setText(it.toString()) } parsed.callAttr("get", "message")?.let { etDescription.setText(it.toString()) } - parsed.callAttr("get", "amount")?.let { amountBox.amount = it.toLong() } + parsed.callAttr("get", "amount")?.let { + try { + amountBox.amount = it.toLong() + } catch (e: PyException) { + throw if (e.message!!.startsWith("OverflowError")) ToastException(e) + else e + } + } amountBox.requestFocus() btnMax.isChecked = false } diff --git a/android/app/src/main/java/org/electroncash/electroncash3/Util.kt b/android/app/src/main/java/org/electroncash/electroncash3/Util.kt index 1076aeb2efc7..de89992ec730 100644 --- a/android/app/src/main/java/org/electroncash/electroncash3/Util.kt +++ b/android/app/src/main/java/org/electroncash/electroncash3/Util.kt @@ -63,7 +63,12 @@ fun toSatoshis(s: String) : Long { val unit = Math.pow(10.0, unitPlaces.toDouble()) try { // toDouble accepts only the English number format: see comment above. - return Math.round(s.toDouble() * unit) + val result = Math.round(s.toDouble() * unit) + return if (result == Long.MAX_VALUE) { + throw ToastException(R.string.Invalid_amount) + } else { + result + } } catch (e: NumberFormatException) { throw ToastException(R.string.Invalid_amount) } From 706ba49a9b184aef114172fc638caaaf9214d2df Mon Sep 17 00:00:00 2001 From: Malcolm Smith Date: Tue, 6 Jul 2021 22:17:59 +0100 Subject: [PATCH 108/208] Android: restore thousands commas in read-only fiat amounts (closes #2246) --- .../main/java/org/electroncash/electroncash3/Addresses.kt | 2 +- .../main/java/org/electroncash/electroncash3/AmountBox.kt | 2 +- .../main/java/org/electroncash/electroncash3/Caption.kt | 2 +- .../main/java/org/electroncash/electroncash3/Exchange.kt | 8 ++++---- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/android/app/src/main/java/org/electroncash/electroncash3/Addresses.kt b/android/app/src/main/java/org/electroncash/electroncash3/Addresses.kt index b3a0f896aeac..9627a64955b3 100644 --- a/android/app/src/main/java/org/electroncash/electroncash3/Addresses.kt +++ b/android/app/src/main/java/org/electroncash/electroncash3/Addresses.kt @@ -155,7 +155,7 @@ class AddressDialog : DetailDialog() { } tvTxCount.movementMethod = LinkMovementMethod.getInstance() - tvBalance.text = ltr(formatSatoshisAndFiat(addrModel.balance)) + tvBalance.text = ltr(formatSatoshisAndFiat(addrModel.balance, commas=true)) } override fun onFirstShowDialog() { diff --git a/android/app/src/main/java/org/electroncash/electroncash3/AmountBox.kt b/android/app/src/main/java/org/electroncash/electroncash3/AmountBox.kt index 9a53f5b5f34e..b65cba55c4b5 100644 --- a/android/app/src/main/java/org/electroncash/electroncash3/AmountBox.kt +++ b/android/app/src/main/java/org/electroncash/electroncash3/AmountBox.kt @@ -31,7 +31,7 @@ class AmountBox(val dialog: Dialog) { dialog.etAmount -> { etOther = dialog.etFiat formatOther = { - formatFiatAmount(toSatoshis(s.toString())) ?: "" + formatFiatAmount(toSatoshis(s.toString()), commas=false) ?: "" } } dialog.etFiat -> { diff --git a/android/app/src/main/java/org/electroncash/electroncash3/Caption.kt b/android/app/src/main/java/org/electroncash/electroncash3/Caption.kt index 32f8c92d9686..279cdac6b038 100644 --- a/android/app/src/main/java/org/electroncash/electroncash3/Caption.kt +++ b/android/app/src/main/java/org/electroncash/electroncash3/Caption.kt @@ -65,7 +65,7 @@ private fun getCaption(): Caption { if (wallet.callAttr("is_fully_settled_down").toBoolean()) { // get_balance returns the tuple (confirmed, unconfirmed, unmatured) val balance = wallet.callAttr("get_balance").asList().get(0).toLong() - subtitle = ltr(formatSatoshisAndFiat(balance)) + subtitle = ltr(formatSatoshisAndFiat(balance, commas=true)) } else { // get_addresses copies the list, which may be very large. val addrCount = wallet.callAttr("get_receiving_addresses").asList().size + diff --git a/android/app/src/main/java/org/electroncash/electroncash3/Exchange.kt b/android/app/src/main/java/org/electroncash/electroncash3/Exchange.kt index 05535cd495ea..b1c39b0f2889 100644 --- a/android/app/src/main/java/org/electroncash/electroncash3/Exchange.kt +++ b/android/app/src/main/java/org/electroncash/electroncash3/Exchange.kt @@ -44,18 +44,18 @@ fun fiatToSatoshis(s: String): Long? { } -fun formatSatoshisAndFiat(amount: Long): String { +fun formatSatoshisAndFiat(amount: Long, commas: Boolean): String { var result = formatSatoshisAndUnit(amount) - val fiat = formatFiatAmount(amount) + val fiat = formatFiatAmount(amount, commas) if (fiat != null) { result += " ($fiat ${formatFiatUnit()})" } return result } -fun formatFiatAmount(amount: Long): String? { +fun formatFiatAmount(amount: Long, commas: Boolean): String? { if (!fiatEnabled()) return null - val amountStr = fx.callAttr("format_amount", amount, Kwarg("commas", false)).toString() + val amountStr = fx.callAttr("format_amount", amount, Kwarg("commas", commas)).toString() return if (amountStr.isEmpty()) null else amountStr } From 11a5ec8a2550943b6499e1e1347ff440f8a70410 Mon Sep 17 00:00:00 2001 From: Malcolm Smith Date: Wed, 7 Jul 2021 14:20:50 +0100 Subject: [PATCH 109/208] Make PaymentRequest.has_expired always return a boolean (closes #2298) --- android/app/build.gradle | 2 +- electroncash/paymentrequest.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index 1c29e69fe032..da55c80d03f3 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -33,7 +33,7 @@ android { if (ecVersion == null) { throw new GradleException("Couldn't find version number") } - def BUILD_NUM = 3 // Distinguish multiple releases with the same version number. + def BUILD_NUM = 4 // Distinguish multiple releases with the same version number. versionName "$ecVersion-$BUILD_NUM" def verParsed = ecVersion.split(/\./).collect { Integer.parseInt(it) } versionCode((verParsed[0] * 1000000) + (verParsed[1] * 10000) + (verParsed[2] * 100) + diff --git a/electroncash/paymentrequest.py b/electroncash/paymentrequest.py index 6ddc3c36d9a9..40ed803b2235 100644 --- a/electroncash/paymentrequest.py +++ b/electroncash/paymentrequest.py @@ -247,7 +247,7 @@ def verify_dnssec(self, pr, contacts): return False def has_expired(self): - return self.details.expires and self.details.expires < int(time.time()) + return bool(self.details.expires and self.details.expires < int(time.time())) def get_expiration_date(self): return self.details.expires From 44ed34597a0f9b55e9da02ef8f9de2501c9c61bd Mon Sep 17 00:00:00 2001 From: Malcolm Smith Date: Wed, 7 Jul 2021 14:40:58 +0100 Subject: [PATCH 110/208] Add missing interface_lock in Network.get_server_height (closes #2173) --- electroncash/network.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/electroncash/network.py b/electroncash/network.py index 062c43144a80..478096938f44 100644 --- a/electroncash/network.py +++ b/electroncash/network.py @@ -418,7 +418,8 @@ def save_recent_servers(self): pass def get_server_height(self): - return self.interface.tip if self.interface else 0 + with self.interface_lock: + return self.interface.tip if self.interface else 0 def server_is_lagging(self): sh = self.get_server_height() From 07b3d1ebe3732f1f3763a4e04427293325926e44 Mon Sep 17 00:00:00 2001 From: Malcolm Smith Date: Wed, 7 Jul 2021 18:49:53 +0100 Subject: [PATCH 111/208] Revert "Android: make sure dialog onPostExecute methods don't run within onStart (closes #2154)" (closes #2244) This reverts commit 9ba78959d61087e8ac5266c837accc8814e020bd. --- .../src/main/java/org/electroncash/electroncash3/App.kt | 7 +++---- .../main/java/org/electroncash/electroncash3/Dialog.kt | 8 ++------ 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/android/app/src/main/java/org/electroncash/electroncash3/App.kt b/android/app/src/main/java/org/electroncash/electroncash3/App.kt index e64cfa8febd7..a865b27ae719 100644 --- a/android/app/src/main/java/org/electroncash/electroncash3/App.kt +++ b/android/app/src/main/java/org/electroncash/electroncash3/App.kt @@ -60,11 +60,10 @@ class App : Application() { } -fun runOnUiThread(r: () -> Unit) { runOnUiThread(Runnable { r() }, false) } -fun postToUiThread(r: () -> Unit) { runOnUiThread(Runnable { r() }, true) } +fun runOnUiThread(r: () -> Unit) { runOnUiThread(Runnable { r() }) } -fun runOnUiThread(r: Runnable, post: Boolean) { - if (onUiThread() && !post) { +fun runOnUiThread(r: Runnable) { + if (onUiThread()) { r.run() } else { mainHandler.post(r) diff --git a/android/app/src/main/java/org/electroncash/electroncash3/Dialog.kt b/android/app/src/main/java/org/electroncash/electroncash3/Dialog.kt index a351b0f4c6dc..2dc035a51343 100644 --- a/android/app/src/main/java/org/electroncash/electroncash3/Dialog.kt +++ b/android/app/src/main/java/org/electroncash/electroncash3/Dialog.kt @@ -246,12 +246,8 @@ abstract class TaskDialog : DialogFragment() { private fun onFinished(body: () -> Unit) { if (model.state == Thread.State.RUNNABLE) { model.state = Thread.State.TERMINATED - - // If we're inside onStart, fragment transactions are unsafe (#2154). - postToUiThread { - body() - dismiss() - } + body() + dismiss() } } From aff02b0c3ea48e692e8007a7c821ec1e55a21223 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?JOE=20LOYA=20=E2=9A=A1=EF=B8=8F?= <45250003+libreloya@users.noreply.github.com> Date: Wed, 7 Jul 2021 18:58:09 -0500 Subject: [PATCH 112/208] [iOS] Support OP_RETURN tansaction outputs (#2307) This pull request concerns the iOS version only. The aim is to bring op_return support to the mobile wallet [#1378]. The desktop version already has op_return support. It accomplishes the following: - Support op_return text message outputs - Support op_return raw hex outputs - Support reading QR codes with op_return params Co-authored-by: lightningloya --- ios/CustomCode/ViewsForIB.h | 4 ++ .../electroncash_gui/ios_native/gui.py | 8 ++- .../electroncash_gui/ios_native/send.py | 47 +++++++++++-- ios/Resources/Send.xib | 68 ++++++++++++++++++- 4 files changed, 121 insertions(+), 6 deletions(-) diff --git a/ios/CustomCode/ViewsForIB.h b/ios/CustomCode/ViewsForIB.h index a1031d39fac7..20892abe4451 100644 --- a/ios/CustomCode/ViewsForIB.h +++ b/ios/CustomCode/ViewsForIB.h @@ -76,6 +76,7 @@ @property (nonatomic, weak) IBOutlet UIButton *contactBut; @property (nonatomic, weak) IBOutlet UILabel *descTit; @property (nonatomic, weak) IBOutlet UITextView *desc; +@property (nonatomic, weak) IBOutlet UITextView *opReturn; @property (nonatomic, weak) IBOutlet UILabel *amtTit; @property (nonatomic, weak) IBOutlet BTCAmountEdit *amt; @property (nonatomic, weak) IBOutlet UIButton *maxBut; @@ -87,14 +88,17 @@ @property (nonatomic, weak) IBOutlet UIBarButtonItem *clearBut; @property (nonatomic, weak) IBOutlet UIBarButtonItem *previewBut; @property (nonatomic, weak) IBOutlet UIButton *sendBut; // actually a subview of a UIBarButtonItem +@property (nonatomic, weak) IBOutlet UIButton *opReturnToggle; @property (nonatomic, weak) IBOutlet UILabel *message; @property (nonatomic, weak) IBOutlet NSLayoutConstraint *csFeeTop, *csTvHeight, *csPayToTop, *csContentHeight; @property (nonatomic, weak) IBOutlet UITableView *tv; @property (nonatomic, weak) IBOutlet UIView *bottomView, *messageView; @property (nonatomic, strong) IBOutlet ECTextViewDelegate *descDel; +@property (nonatomic, strong) IBOutlet ECTextViewDelegate *opReturnDel; @end @interface SendVC : SendBase +-(IBAction)onToggleRawOpReturn; // implemented in python send.py -(IBAction)onQRBut:(id)sender; // implemented in python send.py -(IBAction)onContactBut:(id)sender; // implemented in python send.py -(IBAction)clear; // implemented in python send.py diff --git a/ios/ElectronCash/electroncash_gui/ios_native/gui.py b/ios/ElectronCash/electroncash_gui/ios_native/gui.py index 0530f2c84c99..119d85a63d34 100644 --- a/ios/ElectronCash/electroncash_gui/ios_native/gui.py +++ b/ios/ElectronCash/electroncash_gui/ios_native/gui.py @@ -1238,9 +1238,15 @@ def pay_to_URI(self, URI, showErr : bool = True) -> bool: amount = out.get('amount') label = out.get('label') message = out.get('message') + op_return = out.get('op_return') + op_return_raw = out.get('op_return_raw') + op_return_is_raw = False + if op_return_raw is not None: + op_return_is_raw = True + op_return = op_return_raw # use label as description (not BIP21 compliant) if self.sendVC: - self.sendVC.onPayTo_message_amount_(address,message,amount) + self.sendVC.onPayTo_message_amount_opReturn_isRaw_(address,message,amount,op_return,op_return_is_raw) return True else: self.show_error("Oops! Something went wrong! Email the developers!") diff --git a/ios/ElectronCash/electroncash_gui/ios_native/send.py b/ios/ElectronCash/electroncash_gui/ios_native/send.py index 311b1297e031..822a314d4f59 100644 --- a/ios/ElectronCash/electroncash_gui/ios_native/send.py +++ b/ios/ElectronCash/electroncash_gui/ios_native/send.py @@ -18,6 +18,7 @@ from electroncash import networks from electroncash.address import Address, ScriptOutput from electroncash.paymentrequest import PaymentRequest +from electroncash.transaction import OPReturn from electroncash import bitcoin from .feeslider import FeeSlider from .amountedit import BTCAmountEdit @@ -60,6 +61,7 @@ class SendVC(SendBase): dismissOnAppear = objc_property() kbas = objc_property() queuedPayTo = objc_property() + opReturnIsRaw = objc_property() @objc_method def init(self): @@ -75,6 +77,7 @@ def init(self): self.dismissOnAppear = False self.kbas = None self.queuedPayTo = None + self.opReturnIsRaw = False self.navigationItem.leftItemsSupplementBackButton = True bb = UIBarButtonItem.new().autorelease() @@ -98,6 +101,7 @@ def dealloc(self) -> None: self.excessiveFee = None self.kbas = None self.queuedPayTo = None + self.opReturnIsRaw = None utils.nspy_pop(self) for e in [self.amt, self.fiat, self.payTo]: if e: utils.nspy_pop(e) @@ -209,10 +213,16 @@ def onManualFee(t : ObjCInstance) -> None: # Error Label self.message.text = "" + self.opReturnDel.placeholderFont = UIFont.italicSystemFontOfSize_(14.0) + self.opReturnDel.tv = self.opReturn + self.opReturnDel.text = "" + self.opReturnDel.placeholderText = _("OP_RETURN data (optional).") + self.descDel.placeholderFont = UIFont.italicSystemFontOfSize_(14.0) self.descDel.tv = self.desc self.descDel.text = "" self.descDel.placeholderText = _("Description of the transaction (not mandatory).") + feelbl = self.feeLbl slider = self.feeSlider @@ -260,7 +270,7 @@ def viewWillAppear_(self, animated : bool) -> None: try: qpt = list(self.queuedPayTo) self.queuedPayTo = None - self.onPayTo_message_amount_(qpt[0],qpt[1],qpt[2]) + self.onPayTo_message_amount_opReturn_isRaw_(qpt[0],qpt[1],qpt[2],qpt[3],qpt[4]) except: utils.NSLog("queuedPayTo.. failed with exception: %s",str(sys.exc_info()[1])) @@ -403,9 +413,13 @@ def textFieldShouldReturn_(self, tf : ObjCInstance) -> bool: @objc_method def onPayTo_message_amount_(self, address, message, amount) -> None: + return self.onPayTo_message_amount_opReturn_isRaw_(address, message, amount, None, False) + + @objc_method + def onPayTo_message_amount_opReturn_isRaw_(self, address, message, amount, op_return, op_return_is_raw) -> None: # address if not self.viewIfLoaded: - self.queuedPayTo = [address, message, amount] + self.queuedPayTo = [address, message, amount, op_return, op_return_is_raw] return tf = self.payTo pr = get_PR(self) @@ -433,10 +447,15 @@ def onPayTo_message_amount_(self, address, message, amount) -> None: tf.resignFirstResponder() utils.uitf_redo_attrs(tf) utils.uitf_redo_attrs(self.fiat) + # op_return + self.opReturnDel.text = str(op_return) if op_return is not None else "" + self.opReturnIsRaw = bool(op_return_is_raw) + self.opReturnToggle.setSelected_(self.opReturnIsRaw) + self.opReturn.resignFirstResponder() self.qrScanErr = False self.chkOk() - utils.NSLog("OnPayTo %s %s %s",str(address), str(message), str(amount)) + utils.NSLog("OnPayTo %s %s %s %s %s",str(address), str(message), str(amount), str(op_return), str(op_return_is_raw)) @objc_method def chkOk(self) -> bool: @@ -453,7 +472,7 @@ def chkOk(self) -> bool: self.csPayToTop.constant = 0 f = self.desc.frame - self.csContentHeight.constant = f.origin.y + f.size.height + 125 + self.csContentHeight.constant = f.origin.y + f.size.height + 255 retVal = False errLbl = self.message @@ -527,6 +546,7 @@ def clearAllExceptSpendFrom(self) -> None: tf = self.fiat # label self.descDel.text = "" + self.opReturnDel.text = "" # slider slider = self.feeSlider slider.setValue_animated_(slider.minimumValue,True) @@ -762,6 +782,11 @@ def get_dummy(): utils.uitf_redo_attrs(amount_e) self.chkOk() + @objc_method + def onToggleRawOpReturn(self) -> None: + self.opReturnIsRaw = not bool(self.opReturnIsRaw) + self.opReturnToggle.setSelected_(self.opReturnIsRaw) + @objc_method def onPreviewSendBut_(self, but) -> None: self.view.endEditing_(True) @@ -1021,6 +1046,20 @@ def read_send_form(send : ObjCInstance) -> tuple: # if not self.question(msg): # return + try: + opreturn_message = send.opReturnDel.text + if opreturn_message: + if send.opReturnIsRaw: + outputs.append(OPReturn.output_for_rawhex(opreturn_message)) + else: + outputs.append(OPReturn.output_for_stringdata(opreturn_message)) + except OPReturn.TooLarge as e: + utils.show_alert(send, _("Error"), str(e)) + return None + except OPReturn.Error as e: + utils.show_alert(send, _("Error"), str(e)) + return None + if not outputs: utils.show_alert(send, _("Error"), _('No outputs')) return None diff --git a/ios/Resources/Send.xib b/ios/Resources/Send.xib index 6f95a007c36b..39dfff9a9bc4 100644 --- a/ios/Resources/Send.xib +++ b/ios/Resources/Send.xib @@ -33,6 +33,9 @@ + + + @@ -61,7 +64,7 @@ - + + + + + + + + + + + + + + + + + + + + +