From afdd3d1ffc544fb613383100d8c46d3c828fbecc Mon Sep 17 00:00:00 2001 From: Nick Klockenga Date: Sat, 18 Mar 2023 22:33:12 -0400 Subject: [PATCH 01/10] taproot_bip32_derivations won't be populated for non-change p2tr addresses and therefor my_pubkey will be None. The additional indendation of script.p2tr(my_pubkey) will make it so it's only called when my_pubkey is not None. --- src/seedsigner/models/psbt_parser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/seedsigner/models/psbt_parser.py b/src/seedsigner/models/psbt_parser.py index a76785f8..3a1374be 100644 --- a/src/seedsigner/models/psbt_parser.py +++ b/src/seedsigner/models/psbt_parser.py @@ -148,7 +148,7 @@ class PSBTParser(): leaf_hashes, derivation = list(out.taproot_bip32_derivations.values())[0] der = derivation.derivation my_pubkey = self.root.derive(der) - sc = script.p2tr(my_pubkey) + sc = script.p2tr(my_pubkey) if sc.data == self.psbt.tx.vout[i].script_pubkey.data: is_change = True From 27e64123814740fe0c3b67d5558901d2093cff76 Mon Sep 17 00:00:00 2001 From: Nick Klockenga Date: Sat, 18 Mar 2023 22:59:57 -0400 Subject: [PATCH 02/10] add unit test for p2tr spend with change for psbt_parser --- tests/test_psbt_parser.py | 33 +++++++++++++++++++++++++++++---- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/tests/test_psbt_parser.py b/tests/test_psbt_parser.py index 7fb84c60..6afe3d00 100644 --- a/tests/test_psbt_parser.py +++ b/tests/test_psbt_parser.py @@ -1,8 +1,33 @@ import pytest from mock import MagicMock -from seedsigner.models import PSBTParser +from seedsigner.models import PSBTParser, Seed from embit import psbt +from embit import bip32 +from embit import bip39 +from binascii import a2b_base64, b2a_base64 +from seedsigner.models.settings_definition import SettingsConstants -def test_sig_count(): - print("test") - \ No newline at end of file +def test_p2tr_change_detection(): + psbt_base64 = "cHNidP8BAIkCAAAAAf8upuiIWF1VTgC/Q8ZWRrameRigaXpRcQcBe8ye+TK3AQAAAAAXCgAAAs7BJqsAAAAAIlEgGKqNQ7yF4+yFrrscHnjrbEHiJFExhR903ze43FtOH3BwTgQTAAAAACJRINBe93RcrOYO4UVLLE0y8pzvblOKQWcoQ0obCey8nA5GAAAAAE8BBDWHzwNMUx9OgAAAAJdr+WtwWfVa6IPbpKZ4KgRC0clbm11Gl155IPA27n2FAvQCrFGH6Ac2U0Gcy1IH5f5ltgUBDz2+fe8iqL6JzZdgEDlK7RRWAACAAQAAgAAAAIAAAQB9AgAAAAGAKOOUFIzw9pbRDaZ7F0DYhLImrdMn//OSm++ff5VNdAAAAAAAAQAAAAKsjLwAAAAAABYAFKEcuxvXmB3rWHSqSviP5mrKMZoL2RArvgAAAAAiUSBGU0Lg5fx/ECsB1Z4ZUqXQFSLFnlmpm0rm5R2l599h2AAAAAABASvZECu+AAAAACJRIEZTQuDl/H8QKwHVnhlSpdAVIsWeWambSublHaXn32HYAQMEAAAAACEWF7hZVn7pIDR429kAn/WDeQiWjZey1iGHztsL1H83QLMZADlK7RRWAACAAQAAgAAAAIABAAAAAAAAAAEXIBe4WVZ+6SA0eNvZAJ/1g3kIlo2XstYhh87bC9R/N0CzACEHbJdqWyMxF2eOPr6YRXUJmry04HUbgKyeM2IZeG+NI9AZADlK7RRWAACAAQAAgAAAAIABAAAAAQAAAAEFIGyXalsjMRdnjj6+mEV1CZq8tOB1G4CsnjNiGXhvjSPQAAA=" + + raw = a2b_base64(psbt_base64) + tx = psbt.PSBT.parse(raw) + + mnemonic = "goddess rough corn exclude cream trial fee trumpet million prevent gaze power".split() + pw = "" + seed = Seed(mnemonic, passphrase=pw) + + pp = PSBTParser(p=tx, seed=seed, network=SettingsConstants.REGTEST) + + assert pp.change_data == [ + { + 'output_index': 0, + 'address': 'bcrt1prz4g6saush37epdwhvwpu78td3q7yfz3xxz37axlx7udck6wracq3rwq30', + 'amount': 2871443918, + 'fingerprint': ['394aed14'], + 'derivation_path': ['m/86h/1h/0h/1/1']} + ] + assert pp.spend_amount == 319049328 + assert pp.change_amount == 2871443918 + assert pp.destination_addresses == ['bcrt1p6p00wazu4nnqac29fvky6vhjnnhku5u2g9njss62rvy7e0yuperq86f5ek'] + assert pp.destination_amounts == [319049328] \ No newline at end of file From f9d37879428b98bf5227592e906f6601b8f54140 Mon Sep 17 00:00:00 2001 From: Jean Do Date: Mon, 22 May 2023 09:49:46 -0400 Subject: [PATCH 03/10] handle exceptions which have no message --- src/seedsigner/controller.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/seedsigner/controller.py b/src/seedsigner/controller.py index 4976d6f7..94cc2f6f 100644 --- a/src/seedsigner/controller.py +++ b/src/seedsigner/controller.py @@ -309,7 +309,10 @@ class Controller(Singleton): # So we extract the Exception type and trim off any "foo.bar." namespacing: last_line = traceback.format_exc().splitlines()[-1] exception_type = last_line.split(":")[0].split(".")[-1] - exception_msg = last_line.split(":")[1] + if ":" in last_line: + exception_msg = last_line.split(":")[1] + else: + exception_msg = "" # Scan for the last debugging line that includes a line number reference line_info = None From dae4c476a5b20908495e1fa80005d6e42260fab3 Mon Sep 17 00:00:00 2001 From: overcat <4catcode@gmail.com> Date: Mon, 22 May 2023 22:12:37 +0800 Subject: [PATCH 04/10] Fix seed word letter suggestion bug --- src/seedsigner/gui/screens/seed_screens.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/seedsigner/gui/screens/seed_screens.py b/src/seedsigner/gui/screens/seed_screens.py index ab5d8ed4..74f0cacf 100644 --- a/src/seedsigner/gui/screens/seed_screens.py +++ b/src/seedsigner/gui/screens/seed_screens.py @@ -379,7 +379,8 @@ class SeedMnemonicEntryScreen(BaseTopNavScreen): self.keyboard.render_keys() - elif input in HardwareButtonsConstants.KEYS__LEFT_RIGHT_UP_DOWN: + elif input in HardwareButtonsConstants.KEYS__LEFT_RIGHT_UP_DOWN \ + or input in (Keyboard.ENTER_TOP, Keyboard.ENTER_BOTTOM): if ret_val in self.possible_alphabet: # Live joystick movement; haven't locked this new letter in yet. # Replace the last letter w/the currently selected one. But don't From 3aab03801bde36bc9b9d211529e3d3e7dfe8adce Mon Sep 17 00:00:00 2001 From: Jean Do Date: Mon, 22 May 2023 11:54:25 -0400 Subject: [PATCH 05/10] dire-warning detour if invalid BIP-85 child index --- src/seedsigner/views/seed_views.py | 36 ++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/src/seedsigner/views/seed_views.py b/src/seedsigner/views/seed_views.py index aca803ba..8643699a 100644 --- a/src/seedsigner/views/seed_views.py +++ b/src/seedsigner/views/seed_views.py @@ -942,6 +942,16 @@ class SeedBIP85SelectChildIndexView(View): if ret == RET_CODE__BACK_BUTTON: return Destination(BackStackView) + if not 0 <= int(ret) < 2**31: + return Destination( + SeedBIP85InvalidChildIndexView, + view_args=dict( + seed_num=self.seed_num, + num_words=self.num_words + ), + skip_current_view=True + ) + return Destination( SeedWordsWarningView, view_args=dict( @@ -951,6 +961,32 @@ class SeedBIP85SelectChildIndexView(View): ) +class SeedBIP85InvalidChildIndexView(View): + def __init__(self, seed_num: int, num_words: int): + super().__init__() + self.seed_num = seed_num + self.num_words = num_words + + + def run(self): + DireWarningScreen( + title="BIP-85 Index Error", + show_back_button=False, + status_headline=f"Invalid Child Index", + text=f"BIP-85 Child Index must be between 0 and {2**31-1}.", + button_data=["Try Again"] + ).display() + + return Destination( + SeedBIP85SelectChildIndexView, + view_args=dict( + seed_num=self.seed_num, + num_words=self.num_words + ), + skip_current_view=True + ) + + """**************************************************************************** Seed Words Backup Test From 5abd9fadf29c713d0e8c555fb0dc231513603ef6 Mon Sep 17 00:00:00 2001 From: kdmukai Date: Sun, 4 Jun 2023 11:32:21 -0400 Subject: [PATCH 06/10] Update manual_installation.md --- docs/manual_installation.md | 193 +++++++++++++++++++++++------------- 1 file changed, 126 insertions(+), 67 deletions(-) diff --git a/docs/manual_installation.md b/docs/manual_installation.md index 3fcdc6ba..00f0f5c4 100644 --- a/docs/manual_installation.md +++ b/docs/manual_installation.md @@ -15,11 +15,12 @@ For the following steps you'll need to either connect a keyboard & monitor to th ### Configure the Pi First things first, verify that you are using the correct version of the Raspberry Pi Lite operating system by typing the command: -``` +```bash cat /etc/os-release ``` + The output of this command should match the following text: -``` +```bash PRETTY_NAME="Raspbian GNU/Linux 10 (buster)" NAME="Raspbian GNU/Linux" VERSION_ID="10" @@ -33,7 +34,7 @@ BUG_REPORT_URL="http://www.raspbian.org/RaspbianBugs" ``` Now launch the Raspberry Pi's System Configuration tool using the command: -``` +```bash sudo raspi-config ``` @@ -45,6 +46,8 @@ Set the following: * `Locale`: arrow up and down through the list and select or deselect languages with the spacebar. * Deselect the default language option that is selected * Select `en_US.UTF-8 UTF-8` for US English + * Use the `TAB` button to select `Ok` and press `ENTER` + * On the next screen select `en_US.UTF-8` for the default locale * You will also need to configure the WiFi settings if you are using the #1 option above to connect to the internet When you exit the System Configuration tool, you will be prompted to reboot the system; allow the system to reboot and continue with these instructions. @@ -52,7 +55,7 @@ When you exit the System Configuration tool, you will be prompted to reboot the ### Change the default password Change the system's default password from the default "raspberry". Run the command: -``` +```bash passwd ``` You will be prompted to enter the current password ("raspberry") and then to enter a new password twice. In our prepared release image, the password used is `AirG@pped!`. @@ -60,7 +63,7 @@ You will be prompted to enter the current password ("raspberry") and then to ent ### Install python3.10 ```bash -# install compiler dependencies +# install compiler dependencies; takes ~1 minute on a Pi Zero 1.3 # openssl, libssl-dev: ssl support when pip fetches packages # libsqlite3-dev: required by `coverage` sudo apt update && sudo apt install -y build-essential zlib1g-dev \ @@ -72,26 +75,33 @@ wget https://www.python.org/ftp/python/3.10.10/Python-3.10.10.tgz tar -xzvf Python-3.10.10.tgz cd Python-3.10.10 -# configure takes ~6 minutes to check what is available +# configure takes ~6 minutes on a Pi Zero 1.3 to check what is available ./configure --enable-optimizations -# compiling takes ~80 minutes(!!) on the Pi Zero 1.3 +# compiling takes ~80 minutes(!!) on a Pi Zero 1.3 sudo make altinstall # cleanup cd .. -rm -rf Python-3.10.10* +sudo rm -rf Python-3.10.10* # Make python3.10 the default version sudo update-alternatives --install /usr/bin/python python /usr/local/bin/python3.10 1 sudo update-alternatives --install /usr/bin/python3 python3 /usr/local/bin/python3.10 1 ``` +Manually re-install `python3-apt` to avoid error messages in later steps: +```bash +sudo apt remove --purge python3-apt -y +sudo apt autoremove -y +sudo apt install python3-apt -y +``` + ### Install dependencies Copy this entire box and run it as one command (will take 15-20min to complete): -``` -sudo apt update && sudo apt install -y wiringpi python3-pip \ +```bash +sudo apt update && time sudo apt install -y wiringpi python3-pip \ python3-numpy python-pil libjpeg-dev zlib1g-dev libopenjp2-7 \ git python3-opencv python3-picamera libatlas-base-dev qrencode ``` @@ -102,17 +112,17 @@ sudo apt update && sudo apt install -y wiringpi python3-pip \ SeedSigner requires `zbar` at 0.23.x or higher. Download the binary: -``` +```bash curl -L http://raspbian.raspberrypi.org/raspbian/pool/main/z/zbar/libzbar0_0.23.90-1_armhf.deb --output libzbar0_0.23.90-1_armhf.deb ``` And then install it: -``` +```bash sudo apt install ./libzbar0_0.23.90-1_armhf.deb ``` Cleanup: -``` +```bash rm libzbar0_0.23.90-1_armhf.deb ``` @@ -120,7 +130,7 @@ rm libzbar0_0.23.90-1_armhf.deb This library "provides functions for reading digital inputs and setting digital outputs, using SPI and I2C, and for accessing the system timers." Run each of the following individual steps: -``` +```bash wget http://www.airspayce.com/mikem/bcm2835/bcm2835-1.60.tar.gz tar zxvf bcm2835-1.60.tar.gz cd bcm2835-1.60/ @@ -132,12 +142,12 @@ sudo rm -rf bcm2835-1.60 ``` ### Set up `virtualenv` -``` +```bash python -m pip install virtualenvwrapper ``` Edit your bash profile with the command `nano ~/.profile` and add the following to the end: -``` +```bash export WORKON_HOME=$HOME/.envs export VIRTUALENVWRAPPER_PYTHON=/usr/bin/python3 source /home/pi/.local/bin/virtualenvwrapper.sh @@ -145,58 +155,27 @@ source /home/pi/.local/bin/virtualenvwrapper.sh Then `CTRL-X` and `y` to exit and save changes. Now create the python virtualenv for SeedSigner with these two commands: -``` +```bash source ~/.profile mkvirtualenv seedsigner-env ``` For convenience you can configure your `.profile` to auto-activate the SeedSigner virtualenv when you ssh in. Once again `nano ~/.profile` and add at the end: -``` +```bash workon seedsigner-env ``` -Optional: If you're going to be testing new code on the SeedSigner, you'll find yourself often needing to kill the SeedSigner code that automatically runs at startup (we'll be configuring this further down). As an extra convenience you can list the process id so that you can then kill it from the terminal: -``` -ps aux | grep main.py -``` - -Save your changes with `CTRL-X` and `y`. - -Now when you `ssh` in you'll see something like: -``` -pi 297 65.4 9.7 74096 36736 ? Rsl 09:26 10:29 /home/pi/.envs/seedsigner-env/bin/python main.py -pi 857 0.0 0.4 7332 1876 pts/0 S+ 09:42 0:00 grep --color=auto main.py -``` - -The top line is our SeedSigner code running. To stop it, run: -``` -kill 297 -``` -Where `297` is the process id listed in the output above (it'll be different each time). - ### Download the SeedSigner code: -``` +```bash git clone https://github.com/SeedSigner/seedsigner cd seedsigner ``` -If you want to run a specific branch within the main SeedSigner repo, switch to it with: -``` -git checkout yourtargetbranch -``` - -And if you want to test a pull request (PR), for example PR #123: -``` -git fetch origin pull/123/head:pr_123 -git checkout pr_123 -``` - -where `pr_123` is any name you want to give to the new branch in your local repo that will hold the PR. - ### Install Python `pip` dependencies: ```bash +# Takes 1hr 45min on a Pi Zero 1.3 pip install -r requirements.txt ``` @@ -207,13 +186,37 @@ The fork is required because the main `pyzbar` repo has been abandoned. This [gi * [PR 76](https://github.com/NaturalHistoryMuseum/pyzbar/pull/76/files): enables scanning to continue even when a null byte (`x\00`) is found. * [PR 82](https://github.com/NaturalHistoryMuseum/pyzbar/pull/82): enable `zbar`'s new binary mode. Note that this PR has a trivial bug that was fixed in our fork. -### Configure `systemd` to run SeedSigner at boot: + +### Finish configuring the virtualenv +Set the SeedSigner `src/` directory as the project directory for the virtualenv (this is where the virtualenv will take you when you activate it): +```bash +cd src +setvirtualenvproject ``` + +Test it out: +```bash +# exit the virtualenv +deactivate + +# change dirs to somewhere else +cd ~ + +# activate the virtualenv +workon seedsigner-env + +# you should now be back in the SeedSigner src/ directory +pwd +``` + + +### Configure `systemd` to run SeedSigner at boot: +```bash sudo nano /etc/systemd/system/seedsigner.service ``` Add the following contents to the text file that was created: -``` +```ini [Unit] Description=Seedsigner @@ -229,46 +232,96 @@ WantedBy=multi-user.target _Note: For local dev you'll want to edit the `Restart=always` line to `Restart=no`. This way when your dev code crashes it won't keep trying to restart itself. Note that the UI "Reset" will no longer work when auto-restarts are disabled._ -_Note: Debugging output is completely wiped via routing the output to `/dev/null 2>&1`. When working in local dev, you're better off disabling the `systemd` SeedSigner service and just directly running the app so you can see all the debugging output live._ +_Note: Debugging output is completely wiped via routing the output to `/dev/null 2>&1`. When working in local dev, you'll `kill` the `systemd` SeedSigner service and just directly run the code on demand so you can see all the debugging output live._ Use `CTRL-X` and `y` to exit and save changes. Configure the service to start running (this will restart the seedsigner code automatically at startup and if it crashes): -``` +```bash sudo systemctl enable seedsigner.service ``` Now reboot the Raspberry Pi: -``` +```bash sudo reboot ``` After the Raspberry Pi reboots, you should see the SeedSigner splash screen and the SeedSigner menu subsequently appear on the LCD screen (note that it can take up to 60 seconds for the menu to appear). + +#### Optional: kill `systemd` SeedSigner process on login +If you're going to be testing new code on the device, you'll find yourself often needing to kill the SeedSigner instance that `systemd` automatically runs at startup. + +You can configure your `~/.profile` to find and kill the SeedSigner process when you ssh in. + +`nano ~/.profile` and add at the end: +```bash +# Find the SeedSigner process and kill it +kill $(ps aux | grep '[m]ain.py' | awk '{print $2}') +``` + + ### Further OS modifications Disable and remove the system's virtual memory / swap file with the commands: -``` + +```bash sudo apt remove dphys-swapfile -y sudo apt autoremove -y sudo rm /var/swap ``` -## Local testing and development -For those who will use the SeedSigner installation for testing/development, it can be helpful to change the system's host name so it doesn't potentially conflict with other Raspberry Pis that may already be present on your network. (For those who don't plan to use the installation for testing or development, you can skip this portion of the process.) To change the host name first edit the "hostname" with the command: +## Manually start the SeedSigner code +```bash +# activate the virtualenv if you haven't already +workon seedsigner-env + +# You should now be in the SeedSigner src/ directory. List its contents: +ls + +# You should see the main.py file. Run it: +python main.py ``` + + +## Local testing and development + +### Run specific branches or PRs +The default branch is `dev`. If you want to run a specific release tag or a specific branch: +```bash +# release tag for v0.6.0: +git checkout 0.6.0 +``` + +And if you want to test a pull request (PR), for example PR #123: +```bash +git fetch origin pull/123/head:pr_123 +git checkout pr_123 +``` + +where `pr_123` is any name you want to give to the new branch in your local repo that will hold the PR. + + +### Change the host name +For those who will use the SeedSigner installation for testing/development, it can be helpful to change the system's host name so it doesn't potentially conflict with other Raspberry Pis that may already be present on your network. (For those who don't plan to use the installation for testing or development, you can skip this portion of the process.) To change the host name first edit the "hostname" with the command: + +```bash sudo nano /etc/hostname ``` -and change "raspberrypi" to "seedsigner" (or another name). Use `CTRL-X` and `y` to exit and save changes. You'll also need to edit the "hosts" file with the command: -``` + +and change "raspberrypi" to "seedsigner" (or another name). Use `CTRL-X` and `y` to exit and save changes. + +You'll also need to edit the "hosts" file with the command: +```bash sudo nano /etc/hosts ``` + and change "raspberrypi" to "seedsigner" (or the other name you previously chose). Use `CTRL-X` and `y` to exit and save changes. ### Set a static IP Your local machine that `ssh`s into the SeedSigner can sometimes get confused if you're connecting to different SeedSigners that are all identified as `pi@seedsigner.local`. In this case it helps to set a static ip and just `ssh` directly to that instead. First find your current `nameserver`: -``` +```bash sudo cat /etc/resolv.conf ``` @@ -290,21 +343,26 @@ static domain_name_servers=192.168.1.254 `CTRL-X` and `y` to save changes. After your next reboot, access this SeedSigner using its new static ip: -``` +```bash +# Use the static ip you set above: ssh pi@192.168.1.200 + +# But the hostname will still work, too: +ssh pi@seedsigner.local ``` ### More convenient `ssh` access: Power SeedSigner devs will find themselves connecting to a lot of different SeedSigners. This can cause headaches with `ssh`'s built-in protections; a different device that uses the same `ssh` credentials is normally a potential spoofing attack. But we're doing this to ourselves on purpose and so we can carve out exceptions. On your local machine, run `nano ~/.ssh/config` and add to the end: -``` +```conf host seedsigner.local StrictHostKeyChecking no UserKnownHostsFile /dev/null User pi LogLevel QUIET +# Set this to the static ip you set above: host 192.168.1.200 StrictHostKeyChecking no UserKnownHostsFile /dev/null @@ -323,7 +381,7 @@ The second entry does the same for a specific static ip; you'll want this if you You can also configure the SeedSigner so that you don't have to enter the `pi` password when you `ssh` in. run `ssh-copy-id` with the same values that you connect via `ssh`: -``` +```bash ssh-copy-id pi@seedsigner.local # or if you're connecting over static ip, something like: @@ -337,7 +395,7 @@ _Note: If you don't have any ssh keys on your local machine, you'll need to crea ## Disable wifi/Bluetooth when using other Raspi boards If you plan to use your installation on a Raspberry Pi that is not a Zero version 1.3, but rather on a Raspberry Pi that has WiFi and Bluetooth capabilities, it is a good idea to disable the following WiFi & Bluetooth, as well as other relevant services (assuming you are not creating this installation for testing/development purposes). Enter the followiing commands to disable WiFi, Bluetooth, & other relevant services: -``` +```bash sudo systemctl disable bluetooth.service sudo systemctl disable wpa_supplicant.service sudo systemctl disable dhcpcd.service @@ -346,12 +404,13 @@ sudo systemctl disable networking.service sudo systemctl disable dphys-swapfile.service sudo ifconfig wlan0 down ``` + Please note that if you are using WiFi to connect/interact with your Raspberry Pi, the last command will sever that connection. You can now safely power the Raspberry Pi off from the SeedSigner main menu. If you do not plan to use your installation for testing/development, it is also a good idea to disable WiFi and Bluetooth by editing the config.txt file found in the installation's "boot" partition. You can add the following text to the end of that file with any simple text editor (Windows: Notepad, Mac: TextEdit, Linux: nano): -``` +```ini dtoverlay=disable-bt dtoverlay=pi3-disable-wifi ``` From e64c2dc5ba14f71720e50e4a15c5900556e5d3ad Mon Sep 17 00:00:00 2001 From: kdmukai Date: Sun, 4 Jun 2023 11:42:19 -0400 Subject: [PATCH 07/10] Update manual_installation.md --- docs/manual_installation.md | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/docs/manual_installation.md b/docs/manual_installation.md index 00f0f5c4..addba92c 100644 --- a/docs/manual_installation.md +++ b/docs/manual_installation.md @@ -64,8 +64,8 @@ You will be prompted to enter the current password ("raspberry") and then to ent ### Install python3.10 ```bash # install compiler dependencies; takes ~1 minute on a Pi Zero 1.3 -# openssl, libssl-dev: ssl support when pip fetches packages -# libsqlite3-dev: required by `coverage` +# * openssl, libssl-dev: ssl support when pip fetches packages +# * libsqlite3-dev: required by `coverage` sudo apt update && sudo apt install -y build-essential zlib1g-dev \ libncurses5-dev libgdbm-dev libnss3-dev openssl libssl-dev \ libreadline-dev libffi-dev wget libsqlite3-dev @@ -75,7 +75,7 @@ wget https://www.python.org/ftp/python/3.10.10/Python-3.10.10.tgz tar -xzvf Python-3.10.10.tgz cd Python-3.10.10 -# configure takes ~6 minutes on a Pi Zero 1.3 to check what is available +# Takes ~6 minutes on a Pi Zero 1.3 to check what is available ./configure --enable-optimizations # compiling takes ~80 minutes(!!) on a Pi Zero 1.3 @@ -90,7 +90,7 @@ sudo update-alternatives --install /usr/bin/python python /usr/local/bin/python3 sudo update-alternatives --install /usr/bin/python3 python3 /usr/local/bin/python3.10 1 ``` -Manually re-install `python3-apt` to avoid error messages in later steps: +Manually re-install `python3-apt` to avoid error messages in later steps (though, ironically, you will see the "ModuleNotFoundError: No module named 'apt_pkg'" error message during the `apt remove` step): ```bash sudo apt remove --purge python3-apt -y sudo apt autoremove -y @@ -99,9 +99,9 @@ sudo apt install python3-apt -y ### Install dependencies -Copy this entire box and run it as one command (will take 15-20min to complete): +Copy this entire box and run it as one command (will take a while to complete): ```bash -sudo apt update && time sudo apt install -y wiringpi python3-pip \ +sudo apt update && sudo apt install -y wiringpi python3-pip \ python3-numpy python-pil libjpeg-dev zlib1g-dev libopenjp2-7 \ git python3-opencv python3-picamera libatlas-base-dev qrencode ``` @@ -154,7 +154,7 @@ source /home/pi/.local/bin/virtualenvwrapper.sh ``` Then `CTRL-X` and `y` to exit and save changes. -Now create the python virtualenv for SeedSigner with these two commands: +Now create the virtualenv for SeedSigner: ```bash source ~/.profile mkvirtualenv seedsigner-env @@ -280,6 +280,8 @@ ls # You should see the main.py file. Run it: python main.py + +# To kill the process, use CTRL-C ``` From acc8517faccb8f905006c74260d42ce18a48fab9 Mon Sep 17 00:00:00 2001 From: Jean Do Date: Tue, 6 Jun 2023 13:44:54 -0400 Subject: [PATCH 08/10] added test_handle_exception() to test_controller.py --- src/seedsigner/controller.py | 2 ++ tests/test_controller.py | 55 ++++++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+) diff --git a/src/seedsigner/controller.py b/src/seedsigner/controller.py index 94cc2f6f..8f68c264 100644 --- a/src/seedsigner/controller.py +++ b/src/seedsigner/controller.py @@ -309,6 +309,8 @@ class Controller(Singleton): # So we extract the Exception type and trim off any "foo.bar." namespacing: last_line = traceback.format_exc().splitlines()[-1] exception_type = last_line.split(":")[0].split(".")[-1] + + # Extract the error message, if there is one if ":" in last_line: exception_msg = last_line.split(":")[1] else: diff --git a/tests/test_controller.py b/tests/test_controller.py index bd8b2c18..4dda4f98 100644 --- a/tests/test_controller.py +++ b/tests/test_controller.py @@ -43,6 +43,61 @@ def test_singleton_get_instance_preserves_state(reset_controller): assert controller.unverified_address == "123abc" +def test_handle_exception(reset_controller): + """ Handle exceptions that get caught by the controller """ + + def process_exception_return_error(exception_type, error_msg=None): + try: + if error_msg: + raise exception_type(error_msg) + else: + raise exception_type() + except Exception as e: + destination = controller.handle_exception(e) + return destination.view_args["error"] + + def is_valid_error_structure(error, exception_type, exception_msg): + """ + Exceptions caught by the controller are forwarded to the + UnhandledExceptionView with view_args["error"] being a list + of three strings, ie: [exception_type, line_info, exception_msg] + """ + if len(error) != 3: + return False + + if error[0] not in str(exception_type): + return False + + if type(error[1]) != str: + return False + + if exception_msg: + if exception_msg not in error[2]: + return False + else: + if error[2] != "": + return False + + return True + + # Initialize the controller + controller = Controller.get_instance() + + # Test exceptions with an exception_msg + error = process_exception_return_error(Exception, "foo") + assert is_valid_error_structure(error, Exception, "foo") + + error = process_exception_return_error(KeyError, "key not found") + assert is_valid_error_structure(error, KeyError, "key not found") + + # Test exceptions without an exception_msg + error = process_exception_return_error(Exception, "") + assert is_valid_error_structure(error, Exception, "") + + error = process_exception_return_error(Exception, None) + assert is_valid_error_structure(error, Exception, None) + + def test_missing_settings_get_defaults(reset_controller): """ Should gracefully handle all missing fields from `settings.json` """ From f683bc355e609cefea5c515e97a473eae826eb45 Mon Sep 17 00:00:00 2001 From: Jean Do Date: Tue, 6 Jun 2023 14:46:37 -0400 Subject: [PATCH 09/10] 2 helpers become 1, looping over test cases --- tests/test_controller.py | 63 ++++++++++++++++------------------------ 1 file changed, 25 insertions(+), 38 deletions(-) diff --git a/tests/test_controller.py b/tests/test_controller.py index 4dda4f98..2fce0387 100644 --- a/tests/test_controller.py +++ b/tests/test_controller.py @@ -46,56 +46,43 @@ def test_singleton_get_instance_preserves_state(reset_controller): def test_handle_exception(reset_controller): """ Handle exceptions that get caught by the controller """ - def process_exception_return_error(exception_type, error_msg=None): - try: - if error_msg: - raise exception_type(error_msg) - else: - raise exception_type() - except Exception as e: - destination = controller.handle_exception(e) - return destination.view_args["error"] - - def is_valid_error_structure(error, exception_type, exception_msg): + def process_exception_asserting_valid_error(exception_type, exception_msg=None): """ Exceptions caught by the controller are forwarded to the UnhandledExceptionView with view_args["error"] being a list of three strings, ie: [exception_type, line_info, exception_msg] """ - if len(error) != 3: - return False - - if error[0] not in str(exception_type): - return False - - if type(error[1]) != str: - return False + try: + if exception_msg: + raise exception_type(exception_msg) + else: + raise exception_type() + except Exception as e: + error = controller.handle_exception(e).view_args["error"] + # assert that error structure is valid + assert len(error) == 3 + assert error[0] in str(exception_type) + assert type(error[1]) == str if exception_msg: - if exception_msg not in error[2]: - return False + assert exception_msg in error[2] else: - if error[2] != "": - return False - - return True + assert error[2] == "" # Initialize the controller controller = Controller.get_instance() - # Test exceptions with an exception_msg - error = process_exception_return_error(Exception, "foo") - assert is_valid_error_structure(error, Exception, "foo") - - error = process_exception_return_error(KeyError, "key not found") - assert is_valid_error_structure(error, KeyError, "key not found") - - # Test exceptions without an exception_msg - error = process_exception_return_error(Exception, "") - assert is_valid_error_structure(error, Exception, "") - - error = process_exception_return_error(Exception, None) - assert is_valid_error_structure(error, Exception, None) + exception_tests = [ + # exceptions with an exception_msg + (Exception, "foo"), + (KeyError, "key not found"), + # exceptions without an exception_msg + (Exception, ""), + (Exception, None), + ] + + for exception_type, exception_msg in exception_tests: + process_exception_asserting_valid_error(exception_type, exception_msg) def test_missing_settings_get_defaults(reset_controller): From 28a4999a091c76cf3d87a53eee4ec1a79003b29c Mon Sep 17 00:00:00 2001 From: Nick Klockenga Date: Thu, 29 Jun 2023 17:23:41 -0400 Subject: [PATCH 10/10] - remove unnecessary imports - add test doc string --- tests/test_psbt_parser.py | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/tests/test_psbt_parser.py b/tests/test_psbt_parser.py index 6afe3d00..e0fb5a84 100644 --- a/tests/test_psbt_parser.py +++ b/tests/test_psbt_parser.py @@ -2,12 +2,26 @@ import pytest from mock import MagicMock from seedsigner.models import PSBTParser, Seed from embit import psbt -from embit import bip32 -from embit import bip39 -from binascii import a2b_base64, b2a_base64 +from binascii import a2b_base64 from seedsigner.models.settings_definition import SettingsConstants def test_p2tr_change_detection(): + """ Should successfully detect change in a p2tr to p2tr psbt spend + + PSBT Tx and Wallet Details + - Single Sig Wallet P2TR (Taproot) with no passphrase + - Regtest 394aed14 m/86'/1'/0' tpubDCawGrRg7YdHdFb9p4mmD8GBaZjJegL53FPFRrMkGoLcgLATJfksUs2y1Q7dVzixAkgecazsxEsUuyj3LyDw7eVVYHQyojwrc2hfesK4wXW + - 1 Inputs + - 3,190,493,401 sats + - 2 Outputs + - 1 Output spend to another wallet (bcrt1p6p00wazu4nnqac29fvky6vhjnnhku5u2g9njss62rvy7e0yuperq86f5ek) p2tr address + - 1 Output change + - addresss bcrt1prz4g6saush37epdwhvwpu78td3q7yfz3xxz37axlx7udck6wracq3rwq30) + - amount 2,871,443,918 sats + - Change addresses is index 1/1 + - Fee 155 sats + """ + psbt_base64 = "cHNidP8BAIkCAAAAAf8upuiIWF1VTgC/Q8ZWRrameRigaXpRcQcBe8ye+TK3AQAAAAAXCgAAAs7BJqsAAAAAIlEgGKqNQ7yF4+yFrrscHnjrbEHiJFExhR903ze43FtOH3BwTgQTAAAAACJRINBe93RcrOYO4UVLLE0y8pzvblOKQWcoQ0obCey8nA5GAAAAAE8BBDWHzwNMUx9OgAAAAJdr+WtwWfVa6IPbpKZ4KgRC0clbm11Gl155IPA27n2FAvQCrFGH6Ac2U0Gcy1IH5f5ltgUBDz2+fe8iqL6JzZdgEDlK7RRWAACAAQAAgAAAAIAAAQB9AgAAAAGAKOOUFIzw9pbRDaZ7F0DYhLImrdMn//OSm++ff5VNdAAAAAAAAQAAAAKsjLwAAAAAABYAFKEcuxvXmB3rWHSqSviP5mrKMZoL2RArvgAAAAAiUSBGU0Lg5fx/ECsB1Z4ZUqXQFSLFnlmpm0rm5R2l599h2AAAAAABASvZECu+AAAAACJRIEZTQuDl/H8QKwHVnhlSpdAVIsWeWambSublHaXn32HYAQMEAAAAACEWF7hZVn7pIDR429kAn/WDeQiWjZey1iGHztsL1H83QLMZADlK7RRWAACAAQAAgAAAAIABAAAAAAAAAAEXIBe4WVZ+6SA0eNvZAJ/1g3kIlo2XstYhh87bC9R/N0CzACEHbJdqWyMxF2eOPr6YRXUJmry04HUbgKyeM2IZeG+NI9AZADlK7RRWAACAAQAAgAAAAIABAAAAAQAAAAEFIGyXalsjMRdnjj6+mEV1CZq8tOB1G4CsnjNiGXhvjSPQAAA=" raw = a2b_base64(psbt_base64)