Merge branch 'dev' into 2026-04-22_CI_hardening

This commit is contained in:
Nick Klockenga
2026-07-15 22:22:35 -04:00
committed by GitHub
7 changed files with 109 additions and 31 deletions
+6 -3
View File
@@ -43,9 +43,12 @@ jobs:
tests/requirements.txt
- name: Install dependencies
run: |
sudo apt-get install -y --no-install-recommends libzbar0
pip install -r requirements.txt -r tests/requirements.txt
pip install .
sudo apt-get install libzbar0
python -m pip install --upgrade pip
pip install -r requirements.txt -r tests/requirements.txt -r l10n/requirements-l10n.txt
pip install -e .
- name: Compile translations
run: python setup.py compile_catalog
- name: Test with pytest
run: |
mkdir -p artifacts
+16 -16
View File
@@ -112,23 +112,23 @@ Instructions to build a SeedSigner OS image (using precisely the same process th
## Downloading the Software
Download the current Version (0.8.6) software image that is compatible with your Raspberry Pi Hardware. The Pi Zero 1.3 is the most common and recommended board.
Download the current Version (0.8.7) software image that is compatible with your Raspberry Pi Hardware. The Pi Zero 1.3 is the most common and recommended board.
| Board | Download Image Link/Name |
| --------------------- | --------------------------------- |
|**[Raspberry Pi Zero 1.3](https://www.raspberrypi.com/products/raspberry-pi-zero/)** |[`seedsigner_os.0.8.6.pi0.img`](https://github.com/SeedSigner/seedsigner/releases/download/0.8.6/seedsigner_os.0.8.6.pi0.img) |
|[Raspberry Pi Zero W](https://www.raspberrypi.com/products/raspberry-pi-zero-w/) |[`seedsigner_os.0.8.6.pi0.img`](https://github.com/SeedSigner/seedsigner/releases/download/0.8.6/seedsigner_os.0.8.6.pi0.img) |
|[Raspberry Pi Zero 2 W](https://www.raspberrypi.com/products/raspberry-pi-zero-2-w/) |[`seedsigner_os.0.8.6.pi02w.img`](https://github.com/SeedSigner/seedsigner/releases/download/0.8.6/seedsigner_os.0.8.6.pi02w.img) |
|[Raspberry Pi 1 Model B/B+](https://www.raspberrypi.com/products/raspberry-pi-1-model-b-plus/) |[`seedsigner_os.0.8.6.pi0.img`](https://github.com/SeedSigner/seedsigner/releases/download/0.8.6/seedsigner_os.0.8.6.pi0.img) |
|[Raspberry Pi 2 Model B](https://www.raspberrypi.com/products/raspberry-pi-2-model-b/) |[`seedsigner_os.0.8.6.pi2.img`](https://github.com/SeedSigner/seedsigner/releases/download/0.8.6/seedsigner_os.0.8.6.pi2.img) |
|[Raspberry Pi 3 Model B](https://www.raspberrypi.com/products/raspberry-pi-3-model-b/) |[`seedsigner_os.0.8.6.pi02w.img`](https://github.com/SeedSigner/seedsigner/releases/download/0.8.6/seedsigner_os.0.8.6.pi02w.img) |
|[Raspberry Pi 4 Model B](https://www.raspberrypi.com/products/raspberry-pi-4-model-b/) |[`seedsigner_os.0.8.6.pi4.img`](https://github.com/SeedSigner/seedsigner/releases/download/0.8.6/seedsigner_os.0.8.6.pi4.img) |
|[Raspberry Pi 400](https://www.raspberrypi.com/products/raspberry-pi-400-unit/) |[`seedsigner_os.0.8.6.pi4.img`](https://github.com/SeedSigner/seedsigner/releases/download/0.8.6/seedsigner_os.0.8.6.pi4.img) |
|**[Raspberry Pi Zero 1.3](https://www.raspberrypi.com/products/raspberry-pi-zero/)** |[`seedsigner_os.0.8.7.pi0.img`](https://github.com/SeedSigner/seedsigner/releases/download/0.8.7/seedsigner_os.0.8.7.pi0.img) |
|[Raspberry Pi Zero W](https://www.raspberrypi.com/products/raspberry-pi-zero-w/) |[`seedsigner_os.0.8.7.pi0.img`](https://github.com/SeedSigner/seedsigner/releases/download/0.8.7/seedsigner_os.0.8.7.pi0.img) |
|[Raspberry Pi Zero 2 W](https://www.raspberrypi.com/products/raspberry-pi-zero-2-w/) |[`seedsigner_os.0.8.7.pi02w.img`](https://github.com/SeedSigner/seedsigner/releases/download/0.8.7/seedsigner_os.0.8.7.pi02w.img) |
|[Raspberry Pi 1 Model B/B+](https://www.raspberrypi.com/products/raspberry-pi-1-model-b-plus/) |[`seedsigner_os.0.8.7.pi0.img`](https://github.com/SeedSigner/seedsigner/releases/download/0.8.7/seedsigner_os.0.8.7.pi0.img) |
|[Raspberry Pi 2 Model B](https://www.raspberrypi.com/products/raspberry-pi-2-model-b/) |[`seedsigner_os.0.8.7.pi2.img`](https://github.com/SeedSigner/seedsigner/releases/download/0.8.7/seedsigner_os.0.8.7.pi2.img) |
|[Raspberry Pi 3 Model B](https://www.raspberrypi.com/products/raspberry-pi-3-model-b/) |[`seedsigner_os.0.8.7.pi02w.img`](https://github.com/SeedSigner/seedsigner/releases/download/0.8.7/seedsigner_os.0.8.7.pi02w.img) |
|[Raspberry Pi 4 Model B](https://www.raspberrypi.com/products/raspberry-pi-4-model-b/) |[`seedsigner_os.0.8.7.pi4.img`](https://github.com/SeedSigner/seedsigner/releases/download/0.8.7/seedsigner_os.0.8.7.pi4.img) |
|[Raspberry Pi 400](https://www.raspberrypi.com/products/raspberry-pi-400-unit/) |[`seedsigner_os.0.8.7.pi4.img`](https://github.com/SeedSigner/seedsigner/releases/download/0.8.7/seedsigner_os.0.8.7.pi4.img) |
Note: If you have physically removed the WiFi component from your board, you will still use the image file of the original (un-modified) hardware. (Our files are compiled/based on the *processor* architecture). Although it is better to spend a few minutes upfront to determine which specific Pi hardware/model you have, if you are still unsure which hardware you have, you can try using the pi0.img file. Making an incorrect choice here will not ruin your board, because this is software, not firmware.
**Also download** these 2 signature verification files to the same folder
[The Plaintext manifest file](https://github.com/SeedSigner/seedsigner/releases/download/0.8.6/seedsigner.0.8.6.sha256.txt)
[The Signature of the manifest file](https://github.com/SeedSigner/seedsigner/releases/download/0.8.6/seedsigner.0.8.6.sha256.txt.sig)
[The Plaintext manifest file](https://github.com/SeedSigner/seedsigner/releases/download/0.8.7/seedsigner.0.8.7.sha256.txt)
[The Signature of the manifest file](https://github.com/SeedSigner/seedsigner/releases/download/0.8.7/seedsigner.0.8.7.sha256.txt.sig)
Users familiar with older versions of the SeedSigner software might be surprised with how fast their software downloads now are, because since version 0.6.0 the software image files are now 100x smaller! Each image file is now under 42 Megabytes so your downloads and verifications will be very quick now (and might even seem *too* quick)!
@@ -160,7 +160,7 @@ The result should confirm that 1 key was *either* imported or updated. *Ignore*
Next, you will run the *verify* command on the signature (.sig) file. (*Verify* must be run from inside the same folder that you downloaded the files into earlier.)
```
gpg --verify seedsigner.0.8.6.sha256.txt.sig
gpg --verify seedsigner.0.8.7.sha256.txt.sig
```
When the verify command completes successfully, it should display output like this:
@@ -230,22 +230,22 @@ Now that you have confirmed that you do have the real SeedSigner Project's Publi
**On Linux or OSX:** Run this command
```
shasum -a 256 --ignore-missing --check seedsigner.0.8.6.sha256.txt
shasum -a 256 --ignore-missing --check seedsigner.0.8.7.sha256.txt
```
Note: macOS versions earlier than v11 (Big Sur) do not support the `--ignore-missing` flag. You can omit it and disregard any missing file warnings.
**On Windows (inside Powershell):** Run this command
```
CertUtil -hashfile seedsigner_os.0.8.6.Insert_Your_Pi_Models_binary_here_For_Example_pi02w.img SHA256
CertUtil -hashfile seedsigner_os.0.8.7.Insert_Your_Pi_Models_binary_here_For_Example_pi02w.img SHA256
```
On Windows, you must then manually compare the resulting file hash value to the corresponding hash value shown inside the .SHA256 cleartext file.
<BR>
Wait up to 30 seconds for the command to complete, and it should display:
```
seedsigner_os.0.8.6.[Your_Pi_Model_For_Example:pi02w].img: OK
seedsigner_os.0.8.7.[Your_Pi_Model_For_Example:pi02w].img: OK
```
**If you receive the "OK" message** for your **seedsigner_os.0.8.6.[Your_Pi_Model_For_Example:pi02w].img file**, as shown above, then your verification is fully complete!
**If you receive the "OK" message** for your **seedsigner_os.0.8.7.[Your_Pi_Model_For_Example:pi02w].img file**, as shown above, then your verification is fully complete!
**All of your downloaded files have now been confirmed as both authentic and unaltered!** You can proceed to create/write your MicroSD card😄😄 !!
If your file result shows "FAILED", then you must stop here immediately. Do not continue. Contact us for assistance at the Telegram group address above.
+1
View File
@@ -1 +1,2 @@
Babel==2.16.0
setuptools>=82.0.0
+6 -7
View File
@@ -226,11 +226,13 @@ class SeedMnemonicEntryView(View):
)
if ret == RET_CODE__BACK_BUTTON:
if self.cur_word_index > 0:
return Destination(BackStackView)
else:
# This handles two possible scenarios:
# 1. Backing out of the first word cancels the mnemonic entry process;
# return to whichever `View` routed us here initially.
# 2. Backing out of the current word returns to the previous word.
if self.cur_word_index == 0:
self.controller.storage.discard_pending_mnemonic()
return Destination(MainMenuView)
return Destination(BackStackView)
# ret will be our new mnemonic word
self.controller.storage.update_pending_mnemonic(ret, self.cur_word_index)
@@ -339,9 +341,6 @@ class SeedFinalizeView(View):
elif button_data[selected_menu_num] == self.PASSPHRASE:
return Destination(SeedAddPassphraseView)
elif selected_menu_num == RET_CODE__BACK_BUTTON:
return Destination(BackStackView)
class SeedAddPassphraseView(View):
+6 -4
View File
@@ -1,6 +1,5 @@
import hashlib
import logging
import os
import time
from gettext import gettext as _
@@ -148,9 +147,12 @@ class ToolsImageEntropyMnemonicLengthView(View):
# Build in some hardware-level uniqueness via CPU unique Serial num
try:
stream = os.popen("cat /proc/cpuinfo | grep Serial")
output = stream.read()
serial_num = output.split(":")[-1].strip().encode('utf-8')
serial_num = b''
with open("/proc/cpuinfo", "r") as f:
for line in f:
if "Serial" in line:
serial_num = line.split(":")[-1].strip().encode('utf-8')
break
serial_hash = hashlib.sha256(serial_num)
hash_bytes = serial_hash.digest()
except Exception as e:
+73
View File
@@ -495,6 +495,79 @@ class TestSeedFlows(FlowTest):
class TestSeedEntryBackFlows(FlowTest):
"""
Tests for every BACK exit scenario from SeedMnemonicEntryView and related views.
A naive BackStackView swap can leave resume_main_flow dangling, causing
auto-redirects on stale flow state. These tests verify that BACK navigation
returns to the correct parent view AND that no flow state leaks.
"""
def test_back_from_seed_entry_first_word(self):
"""
Pressing BACK on the first word of mnemonic entry should return to
the View that initiated the mnemonic entry process.
"""
for seed_type in [seed_views.LoadSeedView.TYPE_12WORD, seed_views.LoadSeedView.TYPE_24WORD]:
self.run_sequence([
FlowStep(MainMenuView, button_data_selection=MainMenuView.SEEDS),
FlowStep(seed_views.SeedsMenuView, is_redirect=True), # No seeds loaded; auto-redirects to LoadSeedView
FlowStep(seed_views.LoadSeedView, button_data_selection=seed_type),
FlowStep(seed_views.SeedMnemonicEntryView, screen_return_value=RET_CODE__BACK_BUTTON),
FlowStep(seed_views.LoadSeedView), # Should land here, NOT MainMenuView
])
BaseTest.reset_controller()
def test_back_from_seed_entry_mid_word(self):
"""
Pressing BACK from a middle word (eg. word #2) should return to the
previous SeedMnemonicEntryView (eg. word #1) via the back stack.
"""
self.run_sequence([
FlowStep(MainMenuView, button_data_selection=MainMenuView.SEEDS),
FlowStep(seed_views.SeedsMenuView, is_redirect=True),
FlowStep(seed_views.LoadSeedView, button_data_selection=seed_views.LoadSeedView.TYPE_12WORD),
FlowStep(seed_views.SeedMnemonicEntryView, screen_return_value="abandon"), # word #1
FlowStep(seed_views.SeedMnemonicEntryView, screen_return_value=RET_CODE__BACK_BUTTON), # BACK from word #2
FlowStep(seed_views.SeedMnemonicEntryView), # Returns to word #1
])
# Verify we're back on word #1: word at index 0 should still be set
# from the previous entry, while word at index 1 should be unset.
assert self.controller.storage.get_pending_mnemonic_word(0) == "abandon"
assert self.controller.storage.get_pending_mnemonic_word(1) is None
def test_back_from_seed_entry_via_seed_select(self):
"""
Backing out of mnemonic entry during an active flow must preserve
`resume_main_flow` so the user remains within that flow.
"""
from seedsigner.controller import Controller
from seedsigner.models.settings import SettingsConstants
self.settings.set_value(SettingsConstants.SETTING__MESSAGE_SIGNING, SettingsConstants.OPTION__ENABLED)
def load_signmessage_into_decoder(view):
view.decoder.add_data("signmessage m/84h/0h/0h/0/0 ascii:test message")
self.run_sequence([
FlowStep(MainMenuView, button_data_selection=MainMenuView.SCAN),
FlowStep(scan_views.ScanView, before_run=load_signmessage_into_decoder),
FlowStep(seed_views.SeedSignMessageStartView, is_redirect=True),
FlowStep(seed_views.SeedSelectSeedView, button_data_selection=seed_views.SeedSelectSeedView.TYPE_12WORD),
FlowStep(seed_views.SeedMnemonicEntryView, screen_return_value=RET_CODE__BACK_BUTTON), # BACK on first word
FlowStep(seed_views.SeedSelectSeedView), # Should return here, in the sign message flow
])
# Verify resume_main_flow is still set — user is still in the sign message flow
assert self.controller.resume_main_flow == Controller.FLOW__SIGN_MESSAGE
class TestMessageSigningFlows(FlowTest):
MAINNET_DERIVATION_PATH = "m/84h/0h/0h/0/0"
TESTNET_DERIVATION_PATH = "m/84h/1h/0h/0/0"