diff --git a/.coveragerc b/.coveragerc
new file mode 100644
index 000000000..73637522f
--- /dev/null
+++ b/.coveragerc
@@ -0,0 +1,40 @@
+[run]
+branch = True
+
+[report]
+skip_empty = True
+skip_covered = True
+
+# Omit; need a different approach to test modules with hardware dependencies
+omit =
+ */__init__.py
+ */tests/*
+ */pyzbar/*
+ */gui/*
+
+# Regexes for lines to exclude from consideration
+exclude_lines =
+ # Have to re-enable the standard pragma
+ pragma: no cover
+
+ # Don't complain about missing debug-only code:
+ def __repr__
+ def __str__
+ if self\.debug
+
+ # Don't complain if tests don't hit defensive assertion code:
+ raise AssertionError
+ raise NotImplementedError
+
+ # Don't complain if non-runnable code isn't run:
+ if 0:
+ if __name__ == .__main__.:
+
+ # Don't complain about abstract methods, they aren't run:
+ @(abc\.)?abstractmethod
+
+
+[html]
+directory = coverage_html_report
+skip_empty = True
+skip_covered = False
diff --git a/.gitignore b/.gitignore
index 392faf91b..dcf343d8c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,3 +6,4 @@ src/seedsigner.egg-info/
src/seedsigner/models/settings_definition.json
.idea
*.mo
+.coverage
diff --git a/README.md b/README.md
index 64a7071bc..ba1a354f3 100644
--- a/README.md
+++ b/README.md
@@ -19,7 +19,7 @@
The goal of SeedSigner is to lower the cost and complexity of Bitcoin multi-signature wallet use. To accomplish this goal, SeedSigner offers anyone the opportunity to build a verifiably air-gapped, stateless Bitcoin signing device using inexpensive, publicly available hardware components (usually < $50). SeedSigner helps users save with Bitcoin by assisting with trustless private key generation and multi-signature wallet setup, and helps users transact with Bitcoin via a secure, air-gapped QR-exchange signing model.
-Additional information about the project can be found at [seedsigner.com](https://seedsigner.com).
+Additional information about the project can be found at [SeedSigner.com](https://seedsigner.com).
You can follow [@SeedSigner](https://twitter.com/SeedSigner) on Twitter for the latest project news and developments.
@@ -46,7 +46,7 @@ If you have specific questions about the project, our [Telegram Group](https://t
### Considerations:
* Built for compatibility with Specter Desktop, Sparrow, and BlueWallet Vaults
* Device takes up to 60 seconds to boot before menu appears (be patient!)
-* Always test your setup before transfering larger amounts of bitcoin (try testnet first!)
+* Always test your setup before transferring larger amounts of bitcoin (try Testnet first!)
* Taproot not quite yet supported
* Slightly rotating the screen clockwise or counter-clockwise should resolve lighting/glare issues
* If you think SeedSigner adds value to the Bitcoin ecosystem, please help us spread the word! (tweets, pics, videos, etc.)
@@ -77,60 +77,180 @@ Notes:
# Software Installation
-## Special Note on Minimizing Trust
+## A Special Note On Minimizing Trust
As is the nature of pre-packaged software downloads, downloading and using the prepared SeedSigner release images means implicitly placing trust in the individual preparing those images; in our project the release images are prepared and signed by the eponymous creator of the project, SeedSigner "the person". That individual is additionally the only person in possession of the PGP keys that are used to sign the release images.
However, one of the many advantages of the open source software model is that the need for this kind of trust can be negated by our users' ability to (1) review the project's source code and (2) assemble the operating image necessary to use the software themselves. From our project's inception, instructions to build a SeedSigner operating image (using precisely the same process that is used to create the prepared release images) have been made availabile. We have put a lot of thought and work into making these instructions easy to understand and follow, even for less technical users. These instructions can be found [here](docs/manual_installation.md).
## Downloading the Software
-The quickest and easiest way to install the software is to download the most recent "seedsigner_X_X_X.zip" file in the [software releases](https://github.com/SeedSigner/seedsigner/releases) section of this repository.
+
+Download the current Version (0.6.0) 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.6.0.pi0.img`](https://github.com/SeedSigner/seedsigner/releases/download/0.6.0/seedsigner_os.0.6.0.pi0.img) |
+|[Raspberry Pi Zero W](https://www.raspberrypi.com/products/raspberry-pi-zero-w/) |[`seedsigner_os.0.6.0.pi0.img`](https://github.com/SeedSigner/seedsigner/releases/download/0.6.0/seedsigner_os.0.6.0.pi0.img) |
+|[Raspberry Pi Zero 2 W](https://www.raspberrypi.com/products/raspberry-pi-zero-2-w/) |[`seedsigner_os.0.6.0.pi02w.img`](https://github.com/SeedSigner/seedsigner/releases/download/0.6.0/seedsigner_os.0.6.0.pi02w.img) |
+|[Raspberry Pi 2 Model B](https://www.raspberrypi.com/products/raspberry-pi-2-model-b/) |[`seedsigner_os.0.6.0.pi2.img`](https://github.com/SeedSigner/seedsigner/releases/download/0.6.0/seedsigner_os.0.6.0.pi2.img) |
+|[Raspberry Pi 3 Model B](https://www.raspberrypi.com/products/raspberry-pi-3-model-b/) |[`seedsigner_os.0.6.0.pi02w.img`](https://github.com/SeedSigner/seedsigner/releases/download/0.6.0/seedsigner_os.0.6.0.pi02w.img) |
+|[Raspberry Pi 4 Model B](https://www.raspberrypi.com/products/raspberry-pi-4-model-b/) |[`seedsigner_os.0.6.0.pi4.img`](https://github.com/SeedSigner/seedsigner/releases/download/0.6.0/seedsigner_os.0.6.0.pi4.img) |
+|[Raspberry Pi 400](https://www.raspberrypi.com/products/raspberry-pi-400-unit/) |[`seedsigner_os.0.6.0.pi4.img`](https://github.com/SeedSigner/seedsigner/releases/download/0.6.0/seedsigner_os.0.6.0.pi4.img) |
-After downloading the .zip file, extract the seedsigner .img file, and write it to a MicroSD card (at least 4GB in size or larger). Then install the MicroSD in the assembled hardware and off you go.
+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.
-## Verifying the Software
-You can verify the data integrity and authenticity of the latest release with as little as three commands. This process assumes that you know [how to navigate on a terminal](https://terminalcheatsheet.com/guides/navigate-terminal) and have navigated to the folder where you have these four relevant files present: (This will most likely be your Downloads folder.)
+**also download** these 2 signature verification files to the same folder
+[The Plaintext manifest file](https://github.com/SeedSigner/seedsigner/releases/download/0.6.0/seedsigner.0.6.0.sha256)
+[The Signature of the manifest file](https://github.com/SeedSigner/seedsigner/releases/download/0.6.0/seedsigner.0.6.0.sha256.sig)
-* seedsigner_pubkey.gpg (from the main folder of this repo)
-* seedsigner_0_4_6.img.zip (from the software release)
-* seedsigner_0_4_6.img.zip.sha256 (from the software release)
-* seedsigner_0_4_6.img.zip.sha256.sig (from the software release)
+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)!
-**Note:** The specific version number of the files in your folder might not match the above exactly, but their overall format and amount should be the same.
+Once the files have all finished downloading, follow the steps below to verify the download before continuing on to write the software onto a MicroSD card. Next, insert the MicroSD into your assembled hardware and connect the USB power. Allow about 45 seconds for our logo to appear, and then you can begin using your SeedSigner!
+
+[Our previous software versions are available here](https://github.com/SeedSigner/seedsigner/releases). Choose a specific version and then expand the *Assets* sub-heading to display the .img file binary and also the 2 associated signature files. **Note:** The prior version files will have lower numbers than the scripts and examples provided in this document, but the naming format will be the same, so you can edit them as required for signature verification etc.
+
+
+## Verifying that the downloaded files are authentic (optional but highly recommended!)
+
+You can quickly verify that the software you just downloaded is both authentic and unaltered, by following these instructions.
+We assume you are running the commands from a computer where both [GPG](https://gnupg.org/download/index.html) and [shasum](https://command-not-found.com/shasum) are already installed, and that you also know [how to navigate on a terminal](https://terminalcheatsheet.com/guides/navigate-terminal).
+
+
+### Step 1. Verify that the signature (.sig) file is genuine:
+
+Run GPG's *fetch-keys* command to import the SeedSigner projects public key from the popular online keyserver called *Keybase.io*, into your computers *keychain*.
-This process also assumes you are running the commands from a system where both [GPG](https://gnupg.org/download/index.html) and [shasum](https://command-not-found.com/shasum) are installed and working.
-First make sure that the public key is present in your keychain:
```
-gpg --import seedsigner_pubkey.gpg
+gpg --fetch-keys https://keybase.io/seedsigner/pgp_keys.asc
```
-This command will import the public key, or return:
+The result should confirm that 1 key was *either* imported or updated. *Ignore* any key ID's or email addresses shown.
+
+![SS - Fetchkeys-Keybase PubKey import with Fingerprint shown (New import or update of the key)v3-100pct](https://user-images.githubusercontent.com/91296549/221334414-adc3616c-462e-490e-8492-3dfee367d13a.jpg)
+
+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. The `*`'s in this command will auto-fill the version from your current folder, so it should be copied and pasted as-is.)
```
-key <...> not changed
+gpg --verify seedsigner.0.6.*.sha256.sig
```
-Now you can verify the authenticity of the small text file containing the release's SHA256 hash with the command:
-```
-gpg --verify seedsigner_0_*_*.img.zip.sha256.sig
+When the verify command completes successfully, it should display output like this:
+
+![SS - Verify Command - GPG on Linux - Masked_v4-100pct](https://user-images.githubusercontent.com/91296549/221334135-8ad1f1af-26d2-429a-91ce-ad41703ed38c.jpg)
+The result must display "**Good signature**". Ignore any email addresses - *only* matching Key fingerprints count here. Stop immediately if it displays "*Bad signature*"!
+
+
+On the *last* output line, look at your *rightmost* 16 characters (the 4 blocks of 4).
+**Crucially, we must now check WHO that Primary key fingerprint /ID belongs to.** We will start by looking at Keybase.io to see if it is the *SeedSigner project* 's public key or not.
+
+ About the warning message:
+ Since you are about to match the outputted fingerprint/ID against the proofs at Keybase.io/SeedSigner, and thereby confirm who the pubkey really belongs to-, you can safely ignore this warning message:
+
```
-**Note:** The `*`s in the command above allow the terminal to auto-populate the command with the version number you have in the folder you are in. It should be copied and pasted as is.
+> WARNING: This key is not certified with a trusted signature!
+> There is no indication that the signature belongs to the owner.
+ ```
+
+
+
+
+ More about how the verify command works:
+
+The verify command will attempt to decrypt the signature file (sha256.sig) by trying each public key already imported into your computer. If the public key we just imported (via fetch-keys), manages to: (a) successfully decrypt the .sig file , and (b), that result matches exactly to the clear-text equivalent (.sha256) of the .sig file, then its "a good signature"!
+
+Crucially, we must still manually check who *exactly* owns the Key ID which gave us that "Good signature". Thats what the warning message means- Who does the matching key really belong to? We will start by looking at keybase.io to see if it is "The SeedSigner project"'s public Key or not.
+Note that it is the file hashes of .sig and .sha256 that *verify* compares, not their raw contents.
+
+
+
+
+
+Now to determine ***who*** the Public key ID belongs to: Goto [Keybase.io/SeedSigner](https://keybase.io/seedsigner)
+
+![SS - Keybase Website PubKey visual matching1_Cropped-80pct](https://user-images.githubusercontent.com/91296549/215326193-97c84e35-5570-4e52-bf3f-e86d367c8908.jpg)
+
+
+
+**You must now *manually* compare: The 16 character fingerprint ID (as circled in red above) to, those *rightmost* 16 characters from your *verify* command.**
+
+**If they match exactly, then you have successfully confirmed that your .sig file is authentically from the SeedSigner Project!**
+
-The reponse to this command should include the text:
+Learn more about how keybase.io helps you check that someone (online) is who they say they are:
+
+Keybase.io allows you to independently verify that the public key saved on Keybase.io, is both authentic and that it belongs to the organization it claims to represent.
+ Keybase has already checked the three pubkey file locations cryptographically when they were saved there. You can further verify the key publications if you would like:
+
+ - *via Keybase*: By clicking on any of the three blue badges to see that the "proof" was published at that location. (The blue badge marked as tweet, is in the most human-readable form and it is also a bi-directional link on Twitter)
+or,
+ - *without keybase (out-of-band)*: By using these 3 links directly: [Twitter](https://twitter.com/SeedSigner/status/1530555252373704707), [Github](https://gist.github.com/SeedSigner/5936fa1219b07e28a3672385b605b5d2) and [SeedSigner.com](https://seedsigner.com/keybase.txt). This method can be used if you would like to make an even deeper, independent inspection without relying on Keybase at all, or if the Keybase.io site is no longer valid or it is removed entirely.
+
+Once you have used one of these methods, you will know if the Public Key stored on Keybase, is genuinely from the SeedSinger Project or not.
+
+
+
+
+If the two ID's do *not* match, then you must stop here immediately. Do not continue. Contact us for assistance in the Telegram group address above.
+
+
+
+### Step 2. Verifying that the *software images/binaries* are genuine
+
+Now that you have confirmed that you do have the real SeedSigner Project's Public Key (ie the 16 characters match) - you can return to your terminal window. Running the *shasum* command, is the final verification step and will confirm (via file hashing) that the software code/image files, were also not altered since publication, or even during your download process.
+(Prior to version 0.6.0 , your verify command will check the .zip file which contains the binary files.)
+
+ **On Linux or OSX:** Run this command
```
-Good signature from "seedsigner " [unknown]
+shasum -a 256 --ignore-missing --check seedsigner.0.6.*.sha256
```
-The previous command validates that aforementioned small text file was signed using the private key that matches the published public key associated with the project (an early timestamped record of this public/private key's creation can be found in this [tweet](https://twitter.com/SeedSigner/status/1389617642286329856?s=20)).
-The last step is to make sure the .zip file that you've downloaded, and that contains the released software, is a perfect match to the software that was published by the holder of the private key in the last step. The command for this step is:
+**On Windows (inside Powershell):** Run this command
```
-shasum -a 256 -c seedsigner_0_*_*.img.zip.sha256
+CertUtil -hashfile seedsigner.0.6.0.sha256 SHA256 | findstr /v "hash"
```
-The reponse to this command should include the text:
+On Windows, you must then manually compare the resulting file hash value to the corresponding hash value shown inside the .SHA256 cleartext file.
+
+
+Wait up to 30 seconds for the command to complete, and it should display:
```
-seedsigner_0_4_6.img.zip: OK
+seedsigner_os.0.6.x[Your_Pi_Model_For_Example:pi02w].img: OK
```
+**If you receive the "OK" message** for your **seedsigner_os.0.6.x.[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.
+
+
+
+Please recognize that this process can only validate the software to the extent that the entity that first published the key is an honest actor, and their private key is not compromised or somehow being used by a malicious actor.
+
+
+
+
+## Writing the software onto your MicroSD card
+
+To write the SeedSigner software onto your MicroSD card, there are a few options available:
+| Application | Description | Platform and official Source |
+|--------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------|
+| Balena Etcher | The application is called Etcher, and the company that wrote it is called Balena. Hence *Etcher by Balena* or *Balena Etcher* | [Available for Windows, Mac and Linux](https://www.balena.io/etcher#download-etcher) |
+| Raspberry Pi Imager | Produced by the Raspberry Pi organization. | [Available for Windows, Mac and Linux](https://www.raspberrypi.com/software/) |
+| DD Command Line Utility | Built-in to Linux and MacOS, the DD (Data Duplicator) is a tool for advanced users. If not used carefully it can accidentally format the incorrect disk! | Built-in to Linux and MacOS |
+
+Be sure to download the software from the genuine publisher.
+Either of the Etcher or Pi Imager software is recommended. Some SeedSigner users have reported a better experience with one or the other. So, if the one application doesn’t work well for your particular machine, then please try the other one.
+
+### **General Considerations:**
+The writing and verify steps are very quick from version 0.6.0 upwards, so please pay close attention to your screen.
+Make sure to set any write-protection physical slider on the MicroSD Card Adapter to UN-locked.
+You also don’t need to pre-format the MicroSD beforehand. You *dont* need to unzip any .zip file beforehand.
+Current Etcher and Pi Imager software will perform a verify action (by default) to make sure the card was written successfully! Watching for that verify step to complete successfully can save you a lot of headaches if you later need to troubleshoot issues where your SeedSigner device doesn’t boot up at power on.
+Writing the MicroSd card is also known as flashing.
+It will overwrite everything on the MicroSD card.
+If the one application fails for you, then please try again using our other recommended application.
+Advanced users may want to try the Linux/MacOS *DD* command instead of using Etcher or Pi Imager, however, a reminder is given that DD can overwrite the wrong disk if you are not careful !
+#### **Specific considerations for Windows users:**
+Use the Pi imager software as your first choice on Windows. Windows can sometimes flag the writing of a MicroSD as risky behaviour and hence it may prevent this activity. If this happens, your writing/flashing will fail, hang or wont even begin, in which case you should to try to run the Etcher/Pi-Imager app "As administrator", (right-click and choose that option). It can also be blocked by windows security in some cases, so If you have the (non-default) *Controlled Folder Access* option set to active, try turning that *off* temporarily.
+
+
-There are other steps you can take to verify the software, including examining the hash value in the .sha256 text file, but this one has been documented here because it seems the simplest for most people to follow. Please recognize that this process can only validate the software to the extent that the entity that first published the key is an honest actor, and assumes the private key has remained uncompromised and is not being used by a malicious actor.
---------------
@@ -154,7 +274,7 @@ The upper and lower portions of the enclosure can be printed using a standard FD
* [Lil Pill](https://cults3d.com/en/3d-model/gadget/lil-pill-seedsigner-case) by @_CyberNomad
* [OrangeSurf Case](https://github.com/orangesurf/orangesurf-seedsigner-case) by @OrangeSurfBTC
-* [PS4 Seedsigner](https://www.thingiverse.com/thing:5363525) by @Silexperience
+* [PS4 SeedSigner](https://www.thingiverse.com/thing:5363525) by @Silexperience
* [OpenPill Faceplate](https://www.printables.com/en/model/179924-seedsigner-open-pill-cover-plates-digital-cross-jo) by @Revetuzo
* [Waveshare CoverPlate](https://cults3d.com/en/3d-model/various/seedsigner-coverplate-for-waveshare-1-3-inch-lcd-hat-with-240x240-pixel-display) by @Adathome1
@@ -184,6 +304,21 @@ CompactSeedQR templates:
* [Baseball card template: 12-word Compact SeedQR (21x21)](docs/seed_qr/printable_templates/trading_card_21x21_w12words.pdf)
* [Baseball card template: 24-word Compact SeedQR (25x25)](docs/seed_qr/printable_templates/trading_card_25x25_w24words.pdf)
+
+
+2-sided SeedQR templates - 8 per sheet
+Printing settings - (2-sided)("flip on long edge")("Actual Size")
+If printing on cardstock, adjust your printer settings via its control panel
+
+A4 templates(210mm * 297mm):
+* [21x21 - stores 12-word seeds ONLY in CompactSeedQR format ONLY](docs/seed_qr/printable_templates/21x21_A4_trading_card_2sided.pdf)
+* [25x25 - stores 12-word or 24 word seeds depending on SeedQR format](docs/seed_qr/printable_templates/25x25_A4_trading_card_2sided.pdf)
+* [29x29 - stores 24-word seeds ONLY as plaintext SeedQR format ONLY](docs/seed_qr/printable_templates/29x29_A4_trading_card_2sided.pdf)
+
+Letter templates(8.5in * 11in):
+* [21x21 - stores 12-word seeds ONLY in CompactSeedQR format ONLY](docs/seed_qr/printable_templates/21x21_letter_trading_card_2sided.pdf)
+* [25x25 - stores 12-word or 24 word seeds depending on format](docs/seed_qr/printable_templates/25x25_letter_trading_card_2sided.pdf)
+* [29x29 - stores 24-word seeds ONLY as plaintext SeedQR format ONLY](docs/seed_qr/printable_templates/29x29_letter_trading_card_2sided.pdf)
---------------
# Manual Installation Instructions
diff --git a/docs/seed_qr/printable_templates/21x21_A4_trading_card_2sided.pdf b/docs/seed_qr/printable_templates/21x21_A4_trading_card_2sided.pdf
new file mode 100644
index 000000000..01946eea0
Binary files /dev/null and b/docs/seed_qr/printable_templates/21x21_A4_trading_card_2sided.pdf differ
diff --git a/docs/seed_qr/printable_templates/21x21_letter_trading_card_2sided.pdf b/docs/seed_qr/printable_templates/21x21_letter_trading_card_2sided.pdf
new file mode 100644
index 000000000..40e1d6475
Binary files /dev/null and b/docs/seed_qr/printable_templates/21x21_letter_trading_card_2sided.pdf differ
diff --git a/docs/seed_qr/printable_templates/25x25_A4_trading_card_2sided.pdf b/docs/seed_qr/printable_templates/25x25_A4_trading_card_2sided.pdf
new file mode 100644
index 000000000..4340c517d
Binary files /dev/null and b/docs/seed_qr/printable_templates/25x25_A4_trading_card_2sided.pdf differ
diff --git a/docs/seed_qr/printable_templates/25x25_letter_trading_card_2sided.pdf b/docs/seed_qr/printable_templates/25x25_letter_trading_card_2sided.pdf
new file mode 100644
index 000000000..60691b585
Binary files /dev/null and b/docs/seed_qr/printable_templates/25x25_letter_trading_card_2sided.pdf differ
diff --git a/docs/seed_qr/printable_templates/29x29_A4_trading_card_2sided.pdf b/docs/seed_qr/printable_templates/29x29_A4_trading_card_2sided.pdf
new file mode 100644
index 000000000..c84ffbddd
Binary files /dev/null and b/docs/seed_qr/printable_templates/29x29_A4_trading_card_2sided.pdf differ
diff --git a/docs/seed_qr/printable_templates/29x29_letter_trading_card_2sided.pdf b/docs/seed_qr/printable_templates/29x29_letter_trading_card_2sided.pdf
new file mode 100644
index 000000000..7731f967e
Binary files /dev/null and b/docs/seed_qr/printable_templates/29x29_letter_trading_card_2sided.pdf differ
diff --git a/enclosures/open_pill_mini/Main_Chassis.stl b/enclosures/open_pill_mini/Main_Chassis.stl
new file mode 100644
index 000000000..805dbaea4
Binary files /dev/null and b/enclosures/open_pill_mini/Main_Chassis.stl differ
diff --git a/enclosures/open_pill_mini_w_coverplate/Buttons.stl b/enclosures/open_pill_mini_w_coverplate/Buttons.stl
new file mode 100644
index 000000000..f6e89c44a
Binary files /dev/null and b/enclosures/open_pill_mini_w_coverplate/Buttons.stl differ
diff --git a/enclosures/open_pill_mini_w_coverplate/Lid.stl b/enclosures/open_pill_mini_w_coverplate/Lid.stl
new file mode 100644
index 000000000..356ef4994
Binary files /dev/null and b/enclosures/open_pill_mini_w_coverplate/Lid.stl differ
diff --git a/enclosures/open_pill_mini_w_coverplate/Main_Chassis.stl b/enclosures/open_pill_mini_w_coverplate/Main_Chassis.stl
new file mode 100644
index 000000000..7f31bfd67
Binary files /dev/null and b/enclosures/open_pill_mini_w_coverplate/Main_Chassis.stl differ
diff --git a/enclosures/open_pill_mini_w_coverplate/Thumbstick_PLA.stl b/enclosures/open_pill_mini_w_coverplate/Thumbstick_PLA.stl
new file mode 100644
index 000000000..c22c02c42
Binary files /dev/null and b/enclosures/open_pill_mini_w_coverplate/Thumbstick_PLA.stl differ
diff --git a/enclosures/open_pill_mini_w_coverplate/Thumbstick_TPU.stl b/enclosures/open_pill_mini_w_coverplate/Thumbstick_TPU.stl
new file mode 100644
index 000000000..0de579064
Binary files /dev/null and b/enclosures/open_pill_mini_w_coverplate/Thumbstick_TPU.stl differ
diff --git a/setup.py b/setup.py
index 9aa1e28b4..82dcfb5dd 100644
--- a/setup.py
+++ b/setup.py
@@ -5,7 +5,7 @@
setuptools.setup(
name="seedsigner",
- version="0.5.2",
+ version="0.6.0",
author="SeedSigner",
author_email="author@example.com",
description="Build an offline, airgapped Bitcoin signing device for less than $50!",
diff --git a/src/seedsigner/controller.py b/src/seedsigner/controller.py
index 2cdf85c37..894b01efb 100644
--- a/src/seedsigner/controller.py
+++ b/src/seedsigner/controller.py
@@ -50,7 +50,7 @@ class Controller(Singleton):
rather than at the top in order avoid circular imports.
"""
- VERSION = "0.5.2"
+ VERSION = "0.6.0"
# Declare class member vars with type hints to enable richer IDE support throughout
# the code.
diff --git a/src/seedsigner/gui/screens/scan_screens.py b/src/seedsigner/gui/screens/scan_screens.py
index 1b29af27a..6484fec11 100644
--- a/src/seedsigner/gui/screens/scan_screens.py
+++ b/src/seedsigner/gui/screens/scan_screens.py
@@ -18,7 +18,7 @@
@dataclass
class ScanScreen(BaseScreen):
decoder: DecodeQR = None
- instructions_text: str = "Scan a QR code"
+ instructions_text: str = "< back | Scan a QR code"
resolution: Tuple[int,int] = (480, 480)
framerate: int = 12
render_rect: Tuple[int,int,int,int] = None
diff --git a/src/seedsigner/models/settings.py b/src/seedsigner/models/settings.py
index 35ca33bb4..609d3ddc5 100644
--- a/src/seedsigner/models/settings.py
+++ b/src/seedsigner/models/settings.py
@@ -156,8 +156,11 @@ def get_multiselect_value_display_names(self, attr_name: str) -> List[str]:
raise Exception(f"Unsupported SettingsEntry.type: {settings_entry.type}")
display_names = []
- for value in self._data[attr_name]:
- display_names.append(settings_entry.get_selection_option_display_name_by_value(value))
+ # Iterate through the selection_options list in order to preserve intended sort
+ # order when adding which options are selected.
+ for value, display_name in settings_entry.selection_options:
+ if value in self._data[attr_name]:
+ display_names.append(display_name)
return display_names
diff --git a/src/seedsigner/models/settings_definition.py b/src/seedsigner/models/settings_definition.py
index 9b88ba73b..917e62b77 100644
--- a/src/seedsigner/models/settings_definition.py
+++ b/src/seedsigner/models/settings_definition.py
@@ -36,11 +36,13 @@ class SettingsConstants:
COORDINATOR__NUNCHUK = "nun"
COORDINATOR__SPARROW = "spa"
COORDINATOR__SPECTER_DESKTOP = "spd"
+ COORDINATOR__KEEPER = "kpr"
ALL_COORDINATORS = [
(COORDINATOR__BLUE_WALLET, "BlueWallet"),
(COORDINATOR__NUNCHUK, "Nunchuk"),
(COORDINATOR__SPARROW, "Sparrow"),
(COORDINATOR__SPECTER_DESKTOP, "Specter Desktop"),
+ (COORDINATOR__KEEPER, "Keeper"),
]
LANGUAGE__ENGLISH = "en"
@@ -53,10 +55,10 @@ class SettingsConstants:
BTC_DENOMINATION__THRESHOLD = "thr"
BTC_DENOMINATION__BTCSATSHYBRID = "hyb"
ALL_BTC_DENOMINATIONS = [
- (BTC_DENOMINATION__BTC, "Btc-only"),
- (BTC_DENOMINATION__SATS, "Sats-only"),
+ (BTC_DENOMINATION__BTC, "BTC"),
+ (BTC_DENOMINATION__SATS, "sats"),
(BTC_DENOMINATION__THRESHOLD, "Threshold at 0.01"),
- (BTC_DENOMINATION__BTCSATSHYBRID, "Btc | Sats hybrid"),
+ (BTC_DENOMINATION__BTCSATSHYBRID, "BTC | sats hybrid"),
]
CAMERA_ROTATION__0 = 0
@@ -364,7 +366,12 @@ class SettingsDefinition:
display_name="Coordinator software",
type=SettingsConstants.TYPE__MULTISELECT,
selection_options=SettingsConstants.ALL_COORDINATORS,
- default_value=SettingsConstants.ALL_COORDINATORS),
+ default_value=[
+ SettingsConstants.COORDINATOR__BLUE_WALLET,
+ SettingsConstants.COORDINATOR__NUNCHUK,
+ SettingsConstants.COORDINATOR__SPARROW,
+ SettingsConstants.COORDINATOR__SPECTER_DESKTOP,
+ ]),
SettingsEntry(category=SettingsConstants.CATEGORY__SYSTEM,
attr_name=SettingsConstants.SETTING__BTC_DENOMINATION,
diff --git a/src/seedsigner/views/psbt_views.py b/src/seedsigner/views/psbt_views.py
index a725c6123..59bda2437 100644
--- a/src/seedsigner/views/psbt_views.py
+++ b/src/seedsigner/views/psbt_views.py
@@ -40,19 +40,17 @@ def run(self):
if not PSBTParser.has_matching_input_fingerprint(psbt=self.controller.psbt, seed=seed, network=self.settings.get_value(SettingsConstants.SETTING__NETWORK)):
# Doesn't look like this seed can sign the current PSBT
button_str += " (?)"
-
- if seed.passphrase is not None:
- # TODO: Include lock icon on right side of button
- pass
+
button_data.append((button_str, SeedSignerCustomIconConstants.FINGERPRINT, "blue"))
+
button_data.append(SCAN_SEED)
button_data.append(TYPE_12WORD)
button_data.append(TYPE_24WORD)
if self.controller.psbt_seed:
- if PSBTParser.has_matching_input_fingerprint(psbt=self.controller.psbt, seed=self.controller.psbt_seed, network=self.settings.get_value(SettingsConstants.SETTING__NETWORK)):
- # skip the seed prompt if a seed was previous selected and has matching input fingerprint
- return Destination(PSBTOverviewView)
+ if PSBTParser.has_matching_input_fingerprint(psbt=self.controller.psbt, seed=self.controller.psbt_seed, network=self.settings.get_value(SettingsConstants.SETTING__NETWORK)):
+ # skip the seed prompt if a seed was previous selected and has matching input fingerprint
+ return Destination(PSBTOverviewView)
selected_menu_num = ButtonListScreen(
title="Select Signer",
diff --git a/src/seedsigner/views/scan_views.py b/src/seedsigner/views/scan_views.py
index 03d6e3943..f3ec96df9 100644
--- a/src/seedsigner/views/scan_views.py
+++ b/src/seedsigner/views/scan_views.py
@@ -47,7 +47,7 @@ def run(self):
psbt = self.decoder.get_psbt()
self.controller.psbt = psbt
self.controller.psbt_parser = None
- return Destination(PSBTSelectSeedView)
+ return Destination(PSBTSelectSeedView, skip_current_view=True)
elif self.decoder.is_settings:
from seedsigner.models.settings import Settings
@@ -84,7 +84,7 @@ def run(self):
return Destination(NotYetImplementedView)
self.controller.multisig_wallet_descriptor = descriptor
- return Destination(MultisigWalletDescriptorView)
+ return Destination(MultisigWalletDescriptorView, skip_current_view=True)
elif self.decoder.is_address:
from seedsigner.views.seed_views import AddressVerificationStartView
@@ -93,6 +93,7 @@ def run(self):
return Destination(
AddressVerificationStartView,
+ skip_current_view=True,
view_args={
"address": address,
"script_type": script_type,
@@ -123,6 +124,9 @@ def run(self):
else:
return Destination(NotYetImplementedView)
+ elif self.decoder.is_invalid:
+ raise Exception("QRCode not recognized or not yet supported.")
+
return Destination(MainMenuView)
diff --git a/src/seedsigner/views/seed_views.py b/src/seedsigner/views/seed_views.py
index c6734c8c1..4c945e8dd 100644
--- a/src/seedsigner/views/seed_views.py
+++ b/src/seedsigner/views/seed_views.py
@@ -37,8 +37,7 @@ def __init__(self):
self.seeds = []
for seed in self.controller.storage.seeds:
self.seeds.append({
- "fingerprint": seed.get_fingerprint(self.settings.get_value(SettingsConstants.SETTING__NETWORK)),
- "has_passphrase": seed.passphrase is not None
+ "fingerprint": seed.get_fingerprint(self.settings.get_value(SettingsConstants.SETTING__NETWORK))
})
@@ -375,6 +374,14 @@ def run(self):
addr = self.controller.unverified_address["address"][:7]
VERIFY_ADDRESS += f" {addr}"
button_data.append(VERIFY_ADDRESS)
+
+ if self.controller.psbt:
+ if PSBTParser.has_matching_input_fingerprint(self.controller.psbt, self.seed, network=self.settings.get_value(SettingsConstants.SETTING__NETWORK)):
+ if self.controller.resume_main_flow and self.controller.resume_main_flow == Controller.FLOW__PSBT:
+ # Re-route us directly back to the start of the PSBT flow
+ self.controller.resume_main_flow = None
+ self.controller.psbt_seed = self.seed
+ return Destination(PSBTOverviewView, skip_current_view=True)
button_data.append(SCAN_PSBT)
@@ -530,6 +537,9 @@ def run(self):
).display()
if selected_menu_num == RET_CODE__BACK_BUTTON:
+ # If previous view is SeedOptionsView then that should be where resume_main_flow started (otherwise it would have been skipped).
+ if len(self.controller.back_stack) >= 2 and self.controller.back_stack[-2].View_cls == SeedOptionsView:
+ self.controller.resume_main_flow = None
return Destination(BackStackView)
else:
@@ -747,6 +757,9 @@ def __init__(self, seed_num: int, coordinator: str, derivation_path: str):
elif coordinator == SettingsConstants.COORDINATOR__BLUE_WALLET:
qr_type = QRType.XPUB
+ elif coordinator == SettingsConstants.COORDINATOR__KEEPER:
+ qr_type = QRType.XPUB
+
elif coordinator == SettingsConstants.COORDINATOR__NUNCHUK:
qr_type = QRType.XPUB__UR
@@ -1392,14 +1405,14 @@ def run(self):
sig_type = SettingsConstants.MULTISIG
if self.controller.multisig_wallet_descriptor:
# Can jump straight to the brute-force verification View
- destination = Destination(SeedAddressVerificationView)
+ destination = Destination(SeedAddressVerificationView, skip_current_view=True)
else:
self.controller.resume_main_flow = Controller.FLOW__VERIFY_MULTISIG_ADDR
- destination = Destination(LoadMultisigWalletDescriptorView)
+ destination = Destination(LoadMultisigWalletDescriptorView, skip_current_view=True)
else:
sig_type = SettingsConstants.SINGLE_SIG
- destination = Destination(SeedSingleSigAddressVerificationSelectSeedView)
+ destination = Destination(SeedSingleSigAddressVerificationSelectSeedView, skip_current_view=True)
elif self.controller.unverified_address["script_type"] == SettingsConstants.TAPROOT:
# TODO: add Taproot support
@@ -1477,12 +1490,7 @@ def run(self):
for seed in seeds:
button_str = seed.get_fingerprint(self.settings.get_value(SettingsConstants.SETTING__NETWORK))
-
- if seed.passphrase is not None:
- # TODO: Include lock icon on right side of button
- pass
button_data.append((button_str, SeedSignerCustomIconConstants.FINGERPRINT, "blue"))
-
text = "Select seed to verify"
button_data.append(SCAN_SEED)
diff --git a/src/seedsigner/views/tools_views.py b/src/seedsigner/views/tools_views.py
index 2c5fa017d..e7778bdf0 100644
--- a/src/seedsigner/views/tools_views.py
+++ b/src/seedsigner/views/tools_views.py
@@ -433,10 +433,6 @@ def run(self):
seeds = self.controller.storage.seeds
for seed in seeds:
button_str = seed.get_fingerprint(self.settings.get_value(SettingsConstants.SETTING__NETWORK))
-
- if seed.passphrase is not None:
- # TODO: Include lock icon on right side of button
- pass
button_data.append((button_str, SeedSignerCustomIconConstants.FINGERPRINT, "blue"))
button_data.append(SCAN_SEED)
diff --git a/tests/README.md b/tests/README.md
index aa79d2a72..ae8679c64 100644
--- a/tests/README.md
+++ b/tests/README.md
@@ -24,3 +24,19 @@ Run a specific test:
```
pytest tests/test_this_file.py::test_this_specific_test
```
+
+### Test Coverage
+Run tests and generate test coverage
+```
+coverage run -m pytest
+```
+
+Show the resulting test coverage details:
+```
+coverage report
+```
+
+Generate the html overview:
+```
+coverage html
+```
diff --git a/tests/requirements.txt b/tests/requirements.txt
index 7f265749c..3bfdf0738 100644
--- a/tests/requirements.txt
+++ b/tests/requirements.txt
@@ -1,2 +1,3 @@
+coverage==7.2.1
+mock==4.0.3
pytest==6.2.4
-mock==4.0.3
\ No newline at end of file
diff --git a/tests/test_controller.py b/tests/test_controller.py
index 99c6f7b3f..bd8b2c18e 100644
--- a/tests/test_controller.py
+++ b/tests/test_controller.py
@@ -52,7 +52,7 @@ def test_missing_settings_get_defaults(reset_controller):
assert controller.settings.get_value(SettingsConstants.SETTING__LANGUAGE) == SettingsConstants.LANGUAGE__ENGLISH
assert controller.settings.get_value(SettingsConstants.SETTING__WORDLIST_LANGUAGE) == SettingsConstants.WORDLIST_LANGUAGE__ENGLISH
assert controller.settings.get_value(SettingsConstants.SETTING__PERSISTENT_SETTINGS) == SettingsConstants.OPTION__DISABLED
- assert controller.settings.get_value(SettingsConstants.SETTING__COORDINATORS) == [i for i,j in SettingsConstants.ALL_COORDINATORS]
+ assert controller.settings.get_value(SettingsConstants.SETTING__COORDINATORS) == [i for i,j in SettingsConstants.ALL_COORDINATORS if i!="kpr"]
assert controller.settings.get_value(SettingsConstants.SETTING__BTC_DENOMINATION) == SettingsConstants.BTC_DENOMINATION__THRESHOLD
# Advanced Settings defaults
diff --git a/tests/test_mnemonic_generation.py b/tests/test_mnemonic_generation.py
index a0c04ed87..76298d34f 100644
--- a/tests/test_mnemonic_generation.py
+++ b/tests/test_mnemonic_generation.py
@@ -1,3 +1,4 @@
+import pytest
import random
from embit import bip39
@@ -42,6 +43,35 @@ def test_calculate_checksum():
assert bip39.mnemonic_is_valid(" ".join(mnemonic))
+def test_calculate_checksum_invalid_mnemonics():
+ """
+ Should raise an Exception on a mnemonic that is invalid due to length or using invalid words.
+ """
+ with pytest.raises(Exception) as e:
+ # Mnemonic is too short: 10 words instead of 11
+ partial_mnemonic = "abandon " * 9 + "about"
+ mnemonic_generation.calculate_checksum(partial_mnemonic.split(" "), wordlist_language_code=SettingsConstants.WORDLIST_LANGUAGE__ENGLISH)
+ assert "12- or 24-word" in str(e)
+
+ with pytest.raises(Exception) as e:
+ # Valid mnemonic but unsupported length
+ mnemonic = "devote myth base logic dust horse nut collect buddy element eyebrow visit empty dress jungle"
+ mnemonic_generation.calculate_checksum(mnemonic.split(" "), wordlist_language_code=SettingsConstants.WORDLIST_LANGUAGE__ENGLISH)
+ assert "12- or 24-word" in str(e)
+
+ with pytest.raises(Exception) as e:
+ # Mnemonic is too short: 22 words instead of 23
+ partial_mnemonic = "abandon " * 21 + "about"
+ mnemonic_generation.calculate_checksum(partial_mnemonic.split(" "), wordlist_language_code=SettingsConstants.WORDLIST_LANGUAGE__ENGLISH)
+ assert "12- or 24-word" in str(e)
+
+ with pytest.raises(ValueError) as e:
+ # Invalid BIP-39 word
+ partial_mnemonic = "foobar " * 11 + "about"
+ mnemonic_generation.calculate_checksum(partial_mnemonic.split(" "), wordlist_language_code=SettingsConstants.WORDLIST_LANGUAGE__ENGLISH)
+ assert "not in the dictionary" in str(e)
+
+
def test_calculate_checksum_with_default_final_word():
""" 11-word and 23-word mnemonics use word `0000` as a temp final word to complete
@@ -62,6 +92,22 @@ def test_calculate_checksum_with_default_final_word():
assert mnemonic1 == mnemonic2
+def test_generate_mnemonic_from_bytes():
+ """
+ Should generate a valid BIP-39 mnemonic from entropy bytes
+ """
+ # From iancoleman.io
+ entropy = "3350f6ac9eeb07d2c6209932808aa7f6"
+ expected_mnemonic = "crew marble private differ race truly blush basket crater affair prepare unique".split()
+ mnemonic = mnemonic_generation.generate_mnemonic_from_bytes(bytes.fromhex(entropy))
+ assert mnemonic == expected_mnemonic
+
+ entropy = "5bf41629fce815c3570955e8f45422abd7e2234141bd4d7ec63b741043b98cad"
+ expected_mnemonic = "fossil pass media what life ticket found click trophy pencil anger fish lawsuit balance agree dash estate wage mom trial aerobic system crawl review".split()
+ mnemonic = mnemonic_generation.generate_mnemonic_from_bytes(bytes.fromhex(entropy))
+ assert mnemonic == expected_mnemonic
+
+
def test_verify_against_coldcard_sample():
""" https://coldcard.com/docs/verifying-dice-roll-math """