Browse Source

Dev v1.0.7 (#57)

* PASS1-133: Modify cosign file naming for fully signed binaries. (#34)

* PASS1-133: Modify cosign file naming for fully signed binaries.

* Remove all USE_CRYPTO, it is not used anymore

Also remove function that is not needed.

* Added use of firmware version from header, also fixed a seg fault

* PASS1-135: Fix sticky up or down key (#33)

* PASS1-135: Fix sticky up or down key

* Add comment for input.reset function

* PASS1-128: Add support back for Bitcoin testnet (#29)

* Fixes part of PASS-91

Show address and index of the address being verified

* Second half of fix for ENV1-91

Add better messaging for address range searching
Fix a bug when saving next_addrs (was comparing dicts by ref)

* Fixes PASS1-122

Check change addresses in addition to receive address in "Verify Address"

* Fix comment punctuation

* Show backup filename to user after successful backup (#18)

Fix PASS1-92

* Auto-truncate multisig config names (#19)

Fix PASS1-101

* PASS1-101: Auto-truncate multisig config names (#19)

Fix PASS1-101

* Remove unnecessary comments

* PASS1-92 (#20)

* Show backup filename to user after successful backup

Fix PASS1-92

* Add missing 'card' parameter to `get_backups_folder_path()` calls

* Revert path function changes since 'card' is not available

* PASS1-102: Fix backwards microSD issue

Found that `ErrorCode` in `SD_HandleTypeDef` was not reset after a failure.
Updated `HAL_SD_Init()` to reset it before attempting initialization.

* PASS1-102: Fix backwards microSD issue (#21)

Found that `ErrorCode` in `SD_HandleTypeDef` was not reset after a failure.
Updated `HAL_SD_Init()` to reset it before attempting initialization.

* PASS1-102_b (#22)

* PASS1-102: Fix backwards microSD issue

Found that `ErrorCode` in `SD_HandleTypeDef` was not reset after a failure.
Updated `HAL_SD_Init()` to reset it before attempting initialization.

* Switch back to hard-coded path for now

* PASS1-122_b (#23)

* PASS1-102: Fix backwards microSD issue

Found that `ErrorCode` in `SD_HandleTypeDef` was not reset after a failure.
Updated `HAL_SD_Init()` to reset it before attempting initialization.

* Update user messaging for found/not found case of Verify Address

Fix bug with trailing space at end of line in `word_wrap()`

* Strip ever time through the loop

* PASS1-125: Add Git commit-msg hook to check for Linear ID (#24)

* PASS1-125: Add Git commit-msg hook to check for Linear ID

* Update .githooks/commit-msg

Co-authored-by: Jean Pierre Dudey <jeandudey@hotmail.com>

Co-authored-by: Jean Pierre Dudey <jeandudey@hotmail.com>

* PASS1-122: Minor updates to text (#27)

* PASS1-127: Fix `reuse lint` issues in the repo (#26)

* PASS1-113: Give the user a way to clear the developer pubkey slot (#25)

* PASS1-122: Added "Address Verified" text to new wallet pairing (#28)

* PASS1-122: Minor updates to text

* PASS1-122: Added "Address Verified" text to new wallet pairing

* PASS1-128: Add support back for Bitcoin testnet

Co-authored-by: Ken Carpenter <ken@foundationdevices.com>
Co-authored-by: Ken Carpenter <62639971+FoundationKen@users.noreply.github.com>
Co-authored-by: Jean Pierre Dudey <jeandudey@hotmail.com>

* PASS1-56: Use XFP in backups filename and don't save `backup_num` (#32)

* PASS1-34: Refactor find address code so there is only one copy (#37)

* PASS1-94: Prevent installing user-signed firmware if no user-key installed (#38)

* PASS1-94: Prevent installing user-signed firmware if no user signing key installed

* Fixed case where user pubkey was removed manually

* Fixed text to match other areas where text is used

* Update text message for developer pubkey

* Hard coded user signed field to false

Co-authored-by: Ken Carpenter <62639971+FoundationKen@users.noreply.github.com>

* PASS1-55: Add menu to switch to a different Passphrase without rebooting (#35)

* PASS1-55: Add menu to switch to a different Passphrase without rebooting

* Changed order of menu items in Passphrase menu

* Modified menu titles and removed "a" from inconsistent text

* PASS1-137: Add Justfile support to Gen 1 repo (#36)

* PASS1-137: Add Justfile support to Gen 1 repo

First pass - not all expected commands are added yet

* Update Justfile with fmt command

Add py and c/h formatting
Need to finalize .clang-format file before doing a full reformatting PR

* Refactor Justfiles to separate them out

Also add graphics build commands

* Update Justfiles a bit

Fix formatting of graphics header files in preparation for automatic code formatting

* PASS1-139: Implement code to allow OCD to capture a screenshot over JTAG (#42)

* PASS1-139: Implement code to allow OCD to capture a screenshot over JTAG

* Update sram4.py

* PASS1-132: Remove duplicate file compilation (#39)

* PASS1-78: In display.text_input, split lines based on pixel widths (#41)

* PASS1-78: In display.text_input, split lines based on pixel widths

* Check for StringIO object before calling split_by_char_size

* PASS1-89: Show exported filename when exporting wallet to microSD (#43)

* PASS1-89: Show exported filename when exporting wallet to microSD

* Deleted/commented unnecessary lines

* PASS1-136: Add Specter wallet back once they fix UR issues (#44)

* PASS1-136: Add Specter wallet back once they fix UR issues

* Rebase onto dev-v1.0.7

* Remove passport from export filename

* Remove flag from all wallets besides Specter wallet

* Removed flag from unnecessary field and renamed flag to import

* Renamed multisig_import function

* PASS1-112: Passphrase input dialog improvements (#48)

* PASS1-112: Passphrase input dialog improvements

The passphrase is limited to 64 characters. The line spacing was reduced to make room for 7 lines. 63 capital W's will fill all 7 lines (+1 over), otherwise 64 characters usually takes about 4 lines.

* Add constant for max message length

* TOOL-3: Setup Docker infra for Gen 1 Development (#45)

* Add Dockerfile for building the firmware

Setting up a local environment for building the firmware can be a
painful process. This wraps that process up in a Dockerfile containing
all the deps needed which is then used in the justfile to build the
firmware.

* Add just targets for signing and cleaning

* Change sha target to take a sha and verify it directly

* Add docs for verifying the firmware SHA sum

* Add version param to sign just target

* Update verify-sha output to be more explicit

* PASS1-67: Change unit to sats in settings (#46)

* PASS1-67: Change unit to sats in settings

* Added warnings for Testnet and made the setting volatile

* Added 'chain' removal to schema_evolution and moved Units menu to top

* Moved Units below Change Pin in menu

* TOOL-4: Implement CI for Passport Gen 1 build (#49)

* TOOL-4: Create CI for firmware build

* TOOL-4: Improve handling of git describe output

* TOOL-4: Rename Justfile to match others in repo

* TOOL-4: Add caching and separated Docker building in CI

* TOOL-4: Update CI to push image to local registry service

* TOOL-4: Update CI to allow customizing of D_BASE

* TOOL-4: Change clang format action

* TOOL-4: User correct clang format version

* TOOL-4: YAML :(

* TOOL-4: Update to clang-format-10.0

* TOOL-4: Updaet to 10

* TOOL-4: Build and export the bootloader

* TOOL-4: Add D_BASE to bootload build step

* TOOL-4: Correctly pass D_BASE to bootloader job

* TOOL-4: Update bootloader make path in Justfile

* TOOL-4: Update CI to output tools

* PASS1-140: Add Justfile commands to DEVELOPMENT.md (#51)

* PASS1-140: Add Justfile commands to DEVELOPMENT.md

* Update DEVELOPMENT.md

* Update DEVELOPMENT.md

Co-authored-by: Ken Carpenter <62639971+FoundationKen@users.noreply.github.com>

* PASS1-148: Fix missing address prefixes for testnet (#53)

* PASS1-148: Fix missing address prefixes for testnet

* Add comma separations to sats values

* Casa support added

* Added testnet prefix check to Verify Address process

* PASS1-150: Fixed missing argument in `import_from_psbt()` call (#55)

* PASS1-150: Fixed missing argument in `import_from_psbt()` call

Also fixed typo in function description.

* Added a space between value and label of BTC/sats

* Disable Casa Support

Casa has not approved the support for Passport yet, until then Casa is disabled temporarily.

Co-authored-by: Corey Lakey <corey.lakey@gmail.com>
Co-authored-by: Jean Pierre Dudey <jeandudey@hotmail.com>
Co-authored-by: Alex Sears <searsaw@users.noreply.github.com>
main v1.0.7
Ken Carpenter 3 years ago
committed by GitHub
parent
commit
428a1f0229
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      .dockerignore
  2. 7
      .githooks/commit-msg
  3. 126
      .github/workflows/validate_and_build.yaml
  4. 4
      .gitignore
  5. 31
      DEVELOPMENT.md
  6. 33
      Dockerfile
  7. 100
      Justfile
  8. 8
      README.md
  9. 180
      ports/stm32/Justfile
  10. 2
      ports/stm32/Makefile
  11. 108
      ports/stm32/boards/Passport/.clang-format
  12. 4
      ports/stm32/boards/Passport/.reuse/dep5
  13. 39
      ports/stm32/boards/Passport/bootloader/Justfile
  14. 2
      ports/stm32/boards/Passport/bootloader/Makefile
  15. BIN
      ports/stm32/boards/Passport/bootloader/secrets
  16. 9
      ports/stm32/boards/Passport/graphics/c/Justfile
  17. 8
      ports/stm32/boards/Passport/graphics/py/Justfile
  18. 31
      ports/stm32/boards/Passport/modfoundation.c
  19. 85
      ports/stm32/boards/Passport/modules/actions.py
  20. 8
      ports/stm32/boards/Passport/modules/auth.py
  21. 19
      ports/stm32/boards/Passport/modules/chains.py
  22. 52
      ports/stm32/boards/Passport/modules/choosers.py
  23. 7
      ports/stm32/boards/Passport/modules/constants.py
  24. 36
      ports/stm32/boards/Passport/modules/display.py
  25. 12
      ports/stm32/boards/Passport/modules/export.py
  26. 11
      ports/stm32/boards/Passport/modules/flow.py
  27. 1
      ports/stm32/boards/Passport/modules/menu.py
  28. 4
      ports/stm32/boards/Passport/modules/multisig.py
  29. 87
      ports/stm32/boards/Passport/modules/new_wallet.py
  30. 10
      ports/stm32/boards/Passport/modules/schema_evolution.py
  31. 2
      ports/stm32/boards/Passport/modules/sram4.py
  32. 2
      ports/stm32/boards/Passport/modules/stash.py
  33. 76
      ports/stm32/boards/Passport/modules/utils.py
  34. 41
      ports/stm32/boards/Passport/modules/ux.py
  35. 4
      ports/stm32/boards/Passport/modules/wallets/bitcoin_core.py
  36. 2
      ports/stm32/boards/Passport/modules/wallets/bluewallet.py
  37. 2
      ports/stm32/boards/Passport/modules/wallets/btcpay.py
  38. 2
      ports/stm32/boards/Passport/modules/wallets/caravan.py
  39. 4
      ports/stm32/boards/Passport/modules/wallets/casa.py
  40. 2
      ports/stm32/boards/Passport/modules/wallets/dux_reserve.py
  41. 2
      ports/stm32/boards/Passport/modules/wallets/electrum.py
  42. 2
      ports/stm32/boards/Passport/modules/wallets/fullynoded.py
  43. 12
      ports/stm32/boards/Passport/modules/wallets/generic_json_wallet.py
  44. 2
      ports/stm32/boards/Passport/modules/wallets/gordian.py
  45. 2
      ports/stm32/boards/Passport/modules/wallets/lily.py
  46. 8
      ports/stm32/boards/Passport/modules/wallets/multisig_json.py
  47. 2
      ports/stm32/boards/Passport/modules/wallets/sparrow.py
  48. 4
      ports/stm32/boards/Passport/modules/wallets/specter.py
  49. 2
      ports/stm32/boards/Passport/modules/wallets/sw_wallets.py
  50. 34
      ports/stm32/boards/Passport/modules/wallets/utils.py
  51. 5
      ports/stm32/boards/Passport/modules/wallets/vault.py
  52. 9
      ports/stm32/boards/Passport/modules/wallets/wasabi.py
  53. 1
      ports/stm32/boards/Passport/tools/cosign/Makefile
  54. 81
      ports/stm32/boards/Passport/tools/cosign/cosign.c
  55. 7
      ports/stm32/boards/Passport/tools/version_info/version_info
  56. 5
      ports/stm32/boards/Passport/tools/word_list_gen/word_list_gen.c
  57. 2
      py/dynruntime.mk
  58. 2
      py/mkenv.mk
  59. 2
      py/mkrules.mk
  60. 2
      requirements.txt
  61. 2
      tools/makemanifest.py

1
.dockerignore

@ -0,0 +1 @@
cosign

7
.githooks/commit-msg

@ -2,13 +2,12 @@
# SPDX-FileCopyrightText: 2021 Foundation Devices, Inc. <hello@foundationdevices.com>
# SPDX-License-Identifier: GPL-3.0-or-later
TEAM_PREFIX='PASS1'
commit_regex="^($TEAM_PREFIX-[0-9]+:\ )"
commit_regex="^([A-Z]+[0-9]*-[0-9]+:\ )"
if ! `grep -iqE "$commit_regex" "$1"`; then
if ! grep -iqE "$commit_regex" "$1"; then
echo "=========================================================================================" >&2
echo "Aborting commit. Your commit message must start with a Linear issue ID, colon then space." >&2
echo "Example: '$TEAM_PREFIX-123: ' (To commit anyway, use the --no-verify option)" >&2
echo "Example: 'PASS1-123: ' (To commit anyway, use the --no-verify option)" >&2
echo "=========================================================================================" >&2
exit 1
fi

126
.github/workflows/validate_and_build.yaml

@ -0,0 +1,126 @@
name: Validate and Build
on: [push]
jobs:
lint-py:
runs-on: ubuntu-18.04
steps:
- uses: actions/checkout@v2
- name: Set up Python 3.9
uses: actions/setup-python@v2
with:
python-version: 3.9
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install pycodestyle
- name: Setup just
uses: extractions/setup-just@aa5d15c144db4585980a44ebfdd2cf337c4f14cb
- name: Analysing the code
run: just ports/stm32/lint-py
continue-on-error: true
lint-c:
runs-on: ubuntu-18.04
steps:
- uses: actions/checkout@v2
- name: Analysing the code
uses: jidicula/clang-format-action@7f6b4bf5a7eb211c0872364ccd8072ff8a77ac44
with:
clang-format-version: '10'
check-path: ./ports/stm32
exclude-regex: trezor-firmware
continue-on-error: true
build-firmware:
runs-on: ubuntu-18.04
needs: [lint-py, lint-c]
services:
registry:
image: registry:2
ports:
- 5000:5000
steps:
- name: Checkout
uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
with:
driver-opts: network=host
- name: Cache Docker layers
uses: actions/cache@v2
with:
path: /tmp/.buildx-cache
key: ${{ runner.os }}-buildx-${{ github.sha }}
restore-keys: |
${{ runner.os }}-buildx-
- name: Build the dependency Docker image
uses: docker/build-push-action@v2
with:
push: true
tags: localhost:5000/foundation-devices/firmware-builder:${{ github.sha }}
cache-from: type=local,src=/tmp/.buildx-cache
cache-to: type=local,dest=/tmp/.buildx-cache
- name: Setup just
uses: extractions/setup-just@aa5d15c144db4585980a44ebfdd2cf337c4f14cb
- name: Build the firmware
run: |
echo "$SIGNING_KEY" > signing_key.pem
version=$(git describe --all --match *dev* | awk '{print $NF}' | cut -d '-' -f 2)
just DOCKER_REGISTRY_BASE="$D_BASE" sign signing_key.pem "${version#?}"
env:
SIGNING_KEY: ${{ secrets.UserSigningKey }}
D_BASE: localhost:5000/
- name: Build the bootloader
run: just DOCKER_REGISTRY_BASE="$D_BASE" bootloader-build
env:
D_BASE: localhost:5000/
- name: Build and make tools available
run: just DOCKER_REGISTRY_BASE="$D_BASE" tools
env:
D_BASE: localhost:5000/
- name: Upload built firmware file
uses: actions/upload-artifact@v2
with:
name: firmware.bin
path: ports/stm32/build-Passport/firmware.bin
- name: Upload signed firmware file
uses: actions/upload-artifact@v2
with:
name: firmware-key-user.bin
path: ports/stm32/build-Passport/firmware-key-user.bin
- name: Upload bootloader
uses: actions/upload-artifact@v2
with:
name: bootloader.bin
path: ports/stm32/boards/Passport/bootloader/arm/release/bootloader.bin
- name: Upload cosign
uses: actions/upload-artifact@v2
with:
name: cosign
path: cosign
- name: Upload add-secrets
uses: actions/upload-artifact@v2
with:
name: add-secrets
path: ports/stm32/boards/Passport/tools/add-secrets/x86/release/add-secrets
- name: Upload word_list_gen
uses: actions/upload-artifact@v2
with:
name: word_list_gen
path: ports/stm32/boards/Passport/tools/word_list_gen/word_list_gen

4
.gitignore

@ -55,3 +55,7 @@ ports/stm32/secrets*
ports/stm32/boards/Passport/bootloader/version_info.c
ports/stm32/boards/Passport/bootloader/secrets*
*.pem
.vscode
cosign

31
DEVELOPMENT.md

@ -44,6 +44,21 @@ Several tools are required for building and debugging Passport.
sudo apt install python3-pip
sudo pip3 install rshell # (this should install rshell in /usr/local/)
### Using Justfile commands
To use Just for running commands, first follow the instructions here: https://github.com/casey/just#installation to install Just. Note that `Pillow` must be updated to `8.3.1` for all commands to work properly.
Once Just has been installed, the developer can use `just` commands to perform actions such as building, flashing, resetting and even taking screenshots of the displays screen.
Note that all `just` commands must be run from `ports/stm32/` directory.
Here are some of the most common `just` commands and their usages:
just flash {version} - Builds if necessary, signs with a user key and then flashes the device with the firmware binary created under `build-Passport/`
just reset - Resets the device
just screenshot {filename} - Screenshots the device and saves to the desired filename
See the `Justfile` included in our source for the full list of `just` commands.
## Building
### Open Shell Windows/Tabs
You will need several shell windows or tabs open to interact with the various tools.
@ -67,6 +82,10 @@ You should see it building various `.c` files and freezing `.py` files. Once co
GEN build-Passport/firmware.dfu
GEN build-Passport/firmware.hex
If you are using `just` commands, then building the firmware can be done by running the following command:
just build
#### Code Signing
In order to load the files onto the device, they need to first be signed by two separate keys.
The `cosign` program performs this task, and it needs to be called twice with two separate
@ -91,6 +110,12 @@ You can also dump the contents of the firmware header with the following command
cosign -f build-Passport/firmware-signed-signed.bin -x
If you are using `just` commands, then signing the firmware can be done by running the following command with the desired version:
just sign 1.0.7
It will build the firmware first if necessary.
#### Building the Bootloader
To build the bootloader do the following:
@ -148,6 +173,12 @@ These commands do the following:
- Write the firmware to flash at address 0x8000000
- Reset the MCU and start executing code at address 0x8000000
If you are using `just` commands, ocd and telnet steps are not required and instead, flashing the firmware can be done using the following command with the desired version number:
just flash 1.0.7
It will build and sign the firmware first if necessary.
### RShell Window
We use `rshell` to connect to the MicroPython device over USB serial. Open another shell and run:

33
Dockerfile

@ -0,0 +1,33 @@
FROM ubuntu:18.04 AS cross_build
RUN apt-get update && \
apt-get install -y git make gcc-arm-none-eabi python3 gcc && \
rm -rf /var/lib/apt/lists/*
COPY drivers /workspace/passport-firmware/drivers
COPY docs /workspace/passport-firmware/docs
COPY extmod /workspace/passport-firmware/extmod
COPY lib /workspace/passport-firmware/lib
COPY mpy-cross /workspace/passport-firmware/mpy-cross
COPY py /workspace/passport-firmware/py
WORKDIR /workspace/passport-firmware/mpy-cross
RUN make
FROM ubuntu:18.04 AS cosign_build
WORKDIR /workspace
RUN apt-get update && \
apt-get install -y git make libssl-dev gcc && \
rm -rf /var/lib/apt/lists/*
COPY ports/stm32/boards/Passport/tools/cosign /workspace/passport-firmware/ports/stm32/boards/Passport/tools/cosign
COPY ports/stm32/boards/Passport/include /workspace/passport-firmware/ports/stm32/boards/Passport/include
COPY lib /workspace/passport-firmware/lib
COPY ports/stm32/boards/Passport/common /workspace/passport-firmware/ports/stm32/boards/Passport/common
WORKDIR /workspace/passport-firmware/ports/stm32/boards/Passport/tools/cosign
RUN make
FROM ubuntu:18.04 AS firmware_builder
COPY --from=cosign_build \
/workspace/passport-firmware/ports/stm32/boards/Passport/tools/cosign/x86/release/cosign /usr/bin/cosign
COPY --from=cross_build \
/workspace/passport-firmware/mpy-cross/mpy-cross /usr/bin/mpy-cross
RUN apt-get update && \
apt-get install -y make gcc-arm-none-eabi autotools-dev automake libtool python3 && \
rm -rf /var/lib/apt/lists/*

100
Justfile

@ -0,0 +1,100 @@
export DOCKER_REGISTRY_BASE := ''
commit_sha := `git rev-parse HEAD`
docker_image := 'foundation-devices/firmware-builder:' + commit_sha
base_path := 'ports/stm32'
firmware_path := base_path + '/build-Passport/firmware.bin'
# build the docker image and then the firmware and bootloader
build: docker-build firmware-build bootloader-build
# build the dependency docker image
docker-build:
#!/usr/bin/env bash
set -exo pipefail
docker build -t ${DOCKER_REGISTRY_BASE}{{ docker_image }} .
# build the firmware inside docker
firmware-build:
#!/usr/bin/env bash
set -exo pipefail
docker run --rm -v "$PWD":/workspace \
-w /workspace/{{ base_path }} \
--entrypoint bash \
${DOCKER_REGISTRY_BASE}{{ docker_image }} \
-c 'make BOARD=Passport MPY_CROSS=/usr/bin/mpy-cross'
# build the bootloader inside docker
bootloader-build:
#!/usr/bin/env bash
set -exo pipefail
docker run --rm -v "$PWD":/workspace \
-w /workspace/{{ base_path }} \
--entrypoint bash \
${DOCKER_REGISTRY_BASE}{{ docker_image }} \
-c 'make -C boards/Passport/bootloader'
# build the docker image and get the tools from it
tools: docker-build cosign-tool add-secrets-tool word-list-gen-tool
# get cosign tool from built docker image
cosign-tool:
#!/usr/bin/env bash
set -exo pipefail
docker run --rm -v "$PWD":/workspace \
-w /workspace \
--entrypoint bash \
${DOCKER_REGISTRY_BASE}{{ docker_image }} \
-c 'cp /usr/bin/cosign cosign'
# get add-secrets tool from built docker image
add-secrets-tool:
#!/usr/bin/env bash
set -exo pipefail
docker run --rm -v "$PWD":/workspace \
-w /workspace \
--entrypoint bash \
${DOCKER_REGISTRY_BASE}{{ docker_image }} \
-c 'make -C ports/stm32/boards/Passport/tools/add-secrets'
# get word_list_gen tool from built docker image
word-list-gen-tool:
#!/usr/bin/env bash
set -exo pipefail
docker run --rm -v "$PWD":/workspace \
-w /workspace/ports/stm32/boards/Passport/tools/word_list_gen \
--entrypoint bash \
${DOCKER_REGISTRY_BASE}{{ docker_image }} \
-c 'gcc word_list_gen.c bip39_words.c bytewords_words.c -o word_list_gen'
# run the built firmware through SHA256
verify-sha sha: build
#!/usr/bin/env bash
sha=$(shasum -a 256 {{ firmware_path }} | awk '{print $1}')
echo -e "Expected SHA:\t{{ sha }}"
echo -e "Actual SHA:\t${sha}"
if [ "$sha" = "{{ sha }}" ]; then
echo "Hashes match!"
else
echo "ERROR: Hashes DO NOT match!"
fi
# sign the built firmware using a private key and the cosign tool
sign keypath version filepath=firmware_path: firmware-build
#!/usr/bin/env bash
set -exo pipefail
docker run --rm -v "$PWD":/workspace \
-w /workspace \
--entrypoint bash \
${DOCKER_REGISTRY_BASE}{{ docker_image }} \
-c "cosign -f {{ filepath }} -k {{ keypath }} -v {{ version }}"
# clean firmware build
clean:
docker run --rm -v "$PWD":/workspace \
-w /workspace/{{ base_path }} \
--entrypoint bash \
${DOCKER_REGISTRY_BASE}{{ docker_image }} \
-c "make clean BOARD=Passport"

8
README.md

@ -32,6 +32,14 @@ Code specific to Passport is included in the following folders:
Please see [`DEVELOPMENT.md`](https://github.com/Foundation-Devices/passport/blob/main/DEVELOPMENT.md) for information on developing for Passport.
## Verifying Firmware SHA Sums
To make building and verifying the firmware a simple process, there is a Dockerfile in the project that builds an image to be used to build the firmware. Using [`just`](https://github.com/casey/just), the following command can be used to verify the reproducability of the firmware. Make sure to substitute `<the sha sum>` for the SHA string to verify.
```shell
just verify-sha <the-sha-sum>
```
## Open Source Components
Passport's firmware incorporates open-source software from several third-party projects, as well as other first-party work we open-sourced.

180
ports/stm32/Justfile

@ -0,0 +1,180 @@
# SPDX-FileCopyrightText: 2021 Foundation Devices, Inc. <hello@foundationdevices.com>
#
# SPDX-License-Identifier: GPL-3.0-or-later
# Install dependencies.
deps:
@echo "Not sure we will need this if all deps are setup via Dockerfile"
# Initialize development environment
init: deps
git config core.hooksPath .githooks
# Lint only the python code of the project
lint-py:
pycodestyle --exclude trezor-firmware --statistics .
# Lint only the C code of the project
lint-c:
@echo "TBD"
# Lint only the code of the project
lint-code: lint-py lint-c
# Lint the licensing
lint-license:
reuse lint
# Lint all of the project
lint: lint-code lint-license
#
# Firmware Commands
#
build:
make BOARD=Passport
# Sign current firmware build with the user.pem key and set specified version
sign version="1.0.0": build
@echo "\nAdding user signature...\n"
@cosign -f build-Passport/firmware.bin -k ~/bin/keys/user.pem -v {{version}} > /dev/null
@cosign -f build-Passport/firmware-key-user.bin -x
@echo "\nSigning Complete!"
# Build, sign and flash the firmware with the specified version
flash version="1.0.0": (sign version)
just run-ocd-command "flash write_image erase build-Passport/firmware-key-user.bin 0x8020000"
just reset
# Install a recent Foundation-signed build
flash-foundation version="1.0.0":
just run-ocd-command "flash write_image erase ../../releases/passport-fw-{{version}}.bin 0x8020000"
just reset
# Clean the firmware build
clean:
make BOARD=Passport clean
#
# Misc. Commands
#
# Launch OCD, run a command and then exit
run-ocd-command command:
sudo /usr/local/bin/openocd -f stlink.cfg -c "adapter speed 1000; transport select hla_swd" -f stm32h7x.cfg -c "init; reset halt; {{command}}" -c "exit"
run-ocd-command-no-halt command:
sudo /usr/local/bin/openocd -f stlink.cfg -c "adapter speed 1000; transport select hla_swd" -f stm32h7x.cfg -c "init; {{command}}" -c "exit"
# Build all Python graphics
graphics-py:
just -f boards/Passport/graphics/py/Justfile build
# Build all C graphics (firmware & bootloader)
graphics-c:
just -f boards/Passport/graphics/c/Justfile build
graphics: graphics-py graphics-c
# Reset the Passport
reset:
just run-ocd-command "reset"
# Get the username for use below
user := `whoami`
# Read the "ROM Secrets" from Passport and save them to a file
save-secrets filename="boards/Passport/bootloader/secrets":
just run-ocd-command "dump_image {{filename}} 0x0801FF00 256"
# Running OCD as sudo makes the output file be owned by root, so switch it back to the user
sudo chown {{user}}:{{user}} {{filename}}
secrets:
#!/usr/bin/env bash
# The last bit below redirects stderr to stdout, which the backticks capture into the variable `secrets`
secrets=`just run-ocd-command "mdb 0x0801FF00 256" 2>&1`
secrets=`echo "$secrets" | tail -n 8`
echo -e "Passport ROM Secrets:\n$secrets"
# Calculate all hashes and format it all for GitHub release notes
hash filepath:
#!/usr/bin/env bash
filename=`basename {{filepath}}`
# SHA256
sha=`shasum -b -a 256 {{filepath}} | sed -rn 's/^(.*) .*$/\1/p'`
echo -e "\n\`SHA256: $sha\`"
echo -e "\`(shasum -b -a 256 $filename)\`\n"
# MD5
md5=`mdsum {{filepath}} | sed -rn 's/^(.*) .*$/\1/p'`
echo "\`MD5: $md5\`"
echo -e "\`(md5 $filename or mdsum $filename)\`\n"
# Build Hash
build_hash=`cosign -f {{filepath}} -x | sed -rn 's/^FW Build Hash: (.*)$/\1/p'`
echo -e "\`Build Hash: $build_hash\`"
echo -e "\`(Developers Only)\`\n"
# Run all tests
test:
@echo "TBD"
# Format the project's .py files under boards/Passport/modules
fmt-py:
#!/usr/bin/env bash
pushd boards/Passport/modules
files_to_fmt=`find . -path ./trezor-firmware -prune -false -o -name '*.py'`
autopep8 --max-line-length=120 --in-place $files_to_fmt
popd
# Format the project's .c and .h files under boards/Passport/
fmt-c:
#!/usr/bin/env bash
pushd boards/Passport
files_to_fmt=`find . -path ./trezor-firmware -prune -false -o -name '*.[c|h]'`
clang-format-5.0 -i --style=file $files_to_fmt
popd
# Format the project's source code under boards/Passport
fmt: fmt-py fmt-c
# Convert a raw pixel map to a PNG
convert-screenshot from_file to_file:
#!/usr/bin/python3
from PIL import Image, ImageOps
raw_bits = open('{{from_file}}', 'rb').read()
WIDTH = 230
HEIGHT = 303
SCAN_WIDTH = 240
# Convert
img = Image.frombuffer('1', (SCAN_WIDTH, HEIGHT), raw_bits)
# Crop to actual width (framebuffer is 240 vs 230 for actual display)
img = img.crop((0, 0, WIDTH, HEIGHT))
# Invert since raw image is actually white on black - have to convert to grayscale first since invert() doesn't work
# for 1-bit per pixel black/white images.
img = ImageOps.grayscale(img)
img = ImageOps.invert(img)
# Apply a color shift to make it look nicer
img = ImageOps.colorize(img, (0,0,0,0), '#E0E0E0')
img.save('{{to_file}}')
# Capture a screenshot from Passport via OCD
screenshot filename:
#!/usr/bin/env bash
ADDR_FILE=screenshot-addr.tmp
TMP_FILE=screenshot.tmp
just run-ocd-command-no-halt "dump_image $ADDR_FILE 0x38006920 4"
N=`head -c 4 $ADDR_FILE | od -An --endian=little -t u4`
FRAMEBUFFER_ADDR=`printf '%x\n' $N`
echo FRAMEBUFFER_ADDR=$FRAMEBUFFER_ADDR
just run-ocd-command-no-halt "dump_image screenshot.tmp 0x$FRAMEBUFFER_ADDR 9090"
just convert-screenshot $TMP_FILE {{filename}}
rm -f $TMP_FILE $ADDR_FILE

2
ports/stm32/Makefile

@ -599,7 +599,7 @@ GEN_CDCINF_FILE = $(HEADER_BUILD)/pybcdc.inf
GEN_CDCINF_HEADER = $(HEADER_BUILD)/pybcdc_inf.h
# List of sources for qstr extraction
SRC_QSTR += $(SRC_C) $(SRC_MOD) $(SRC_LIB) $(EXTMOD_SRC_C)
SRC_QSTR += $(SRC_C) $(SRC_LIB) $(EXTMOD_SRC_C)
# Append any auto-generated sources that are needed by sources listed in
# SRC_QSTR
SRC_QSTR_AUTO_DEPS += $(GEN_CDCINF_HEADER)

108
ports/stm32/boards/Passport/.clang-format

@ -1,7 +1,107 @@
---
# We'll use defaults from the LLVM style, but with 4 columns indentation.
BasedOnStyle: Mozilla
IndentWidth: 4
---
Language: Cpp
# BasedOnStyle: Chromium
AccessModifierOffset: -1
AlignAfterOpenBracket: Align
AlignConsecutiveAssignments: true
AlignConsecutiveDeclarations: true
AlignEscapedNewlines: Left
AlignOperands: true
AlignTrailingComments: true
AllowAllParametersOfDeclarationOnNextLine: true
AllowShortBlocksOnASingleLine: false
AllowShortCaseLabelsOnASingleLine: false
AllowShortFunctionsOnASingleLine: Inline
AllowShortIfStatementsOnASingleLine: true
AllowShortLoopsOnASingleLine: false
AlwaysBreakAfterDefinitionReturnType: None
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: true
AlwaysBreakTemplateDeclarations: true
BinPackArguments: true
BinPackParameters: false
BraceWrapping:
AfterClass: false
AfterControlStatement: false
AfterEnum: false
AfterFunction: false
AfterNamespace: false
AfterObjCDeclaration: false
AfterStruct: false
AfterUnion: false
BeforeCatch: false
BeforeElse: false
IndentBraces: false
SplitEmptyFunction: true
SplitEmptyRecord: true
SplitEmptyNamespace: true
BreakBeforeBinaryOperators: None
BreakBeforeBraces: Attach
BreakBeforeInheritanceComma: false
BreakBeforeTernaryOperators: true
BreakConstructorInitializersBeforeComma: false
BreakConstructorInitializers: BeforeColon
BreakAfterJavaFieldAnnotations: false
BreakStringLiterals: false
ColumnLimit: 120
CommentPragmas: '^ IWYU pragma:'
CompactNamespaces: false
ConstructorInitializerAllOnOneLineOrOnePerLine: true
ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 4
Cpp11BracedListStyle: true
DerivePointerAlignment: false
DisableFormat: false
ExperimentalAutoDetectBinPacking: false
FixNamespaceComments: true
ForEachMacros:
- foreach
- Q_FOREACH
- BOOST_FOREACH
IncludeCategories:
- Regex: '^<.*\.h>'
Priority: 1
- Regex: '^<.*'
Priority: 2
- Regex: '.*'
Priority: 3
IncludeIsMainRegex: '([-_](test|unittest))?$'
IndentCaseLabels: true
IndentWidth: 4
IndentWrappedFunctionNames: false
JavaScriptQuotes: Leave
JavaScriptWrapImports: true
KeepEmptyLinesAtTheStartOfBlocks: false
MacroBlockBegin: ''
MacroBlockEnd: ''
MaxEmptyLinesToKeep: 1
NamespaceIndentation: None
ObjCBlockIndentWidth: 2
ObjCSpaceAfterProperty: false
ObjCSpaceBeforeProtocolList: false
PenaltyBreakAssignment: 2
PenaltyBreakBeforeFirstCallParameter: 1
PenaltyBreakComment: 300
PenaltyBreakFirstLessLess: 120
PenaltyBreakString: 1000
PenaltyExcessCharacter: 1000000
PenaltyReturnTypeOnItsOwnLine: 200
PointerAlignment: Left
ReflowComments: false
SortIncludes: true
SortUsingDeclarations: true
SpaceAfterCStyleCast: false
SpaceAfterTemplateKeyword: true
SpaceBeforeAssignmentOperators: true
SpaceBeforeParens: ControlStatements
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 2
SpacesInAngles: false
SpacesInContainerLiterals: true
SpacesInCStyleCastParentheses: false
SpacesInParentheses: false
SpacesInSquareBrackets: false
Standard: Auto
TabWidth: 8
UseTab: Never
...

4
ports/stm32/boards/Passport/.reuse/dep5

@ -18,3 +18,7 @@ License: GPL-3.0-or-later
Files: .vscode/settings.json TODO.txt bootloader/se-config.h clang-format.txt include/se-config.h modules/graphics.py pins.csv utils/README.md
Copyright: 2020 Foundation Devices, Inc. <hello@foundationdevices.com>
License: GPL-3.0-or-later
Files: tools/version_info/version_info
Copyright: 2021 Foundation Devices, Inc. <hello@foundationdevices.com>
License: GPL-3.0-or-later

39
ports/stm32/boards/Passport/bootloader/Justfile

@ -0,0 +1,39 @@
# SPDX-FileCopyrightText: 2021 Foundation Devices, Inc. <hello@foundationdevices.com>
#
# SPDX-License-Identifier: GPL-3.0-or-later
# Launch OCD, run a command and then exit
run-ocd-command command:
cd ../../../; sudo /usr/local/bin/openocd -f stlink.cfg -c "adapter speed 1000; transport select hla_swd" -f stm32h7x.cfg -c "init; reset halt; {{command}}" -c "exit"
# Build the bootloader (debug, release, locked or production)
# TODO: Need to handle {{rel}} for locked and production, which should look in release folder for binary
build rel="release":
@echo "\nBuilding Bootloader..."
make {{rel}}
@echo "\nAppending secrets to the end..."
add-secrets -b arm/{{rel}}/bootloader.bin -s secrets
@echo "\nBootloader Build Complete"
# Clean the bootloader build
clean:
@echo "Cleaning Bootloader..."
make clean
@echo "Bootloader Clean Complete"
# Build and flash the bootloader with the secrets appended to the end
flash rel="release": (build rel)
just run-ocd-command "flash write_image erase boards/Passport/bootloader/arm/{{rel}}/bootloader-secrets.bin 0x8000000"
just reset
# Build and flash the bootloader with no secrets (use to setup a new Secure Element)
flash-raw rel="release": (build rel)
just run-ocd-command "flash write_image erase boards/Passport/bootloader/arm/{{rel}}/bootloader.bin 0x8000000"
just reset
# Reset the Passport
reset:
just run-ocd-command "reset"

2
ports/stm32/boards/Passport/bootloader/Makefile

@ -207,7 +207,7 @@ ifneq ($(MAKECMDGOALS),clean)
endif
# make a 'release' build
release: code-committed check-fontawesome clean all capture
release: clean all
release: CFLAGS += -DRELEASE=1 -Werror
clean:

BIN
ports/stm32/boards/Passport/bootloader/secrets

Binary file not shown.

9
ports/stm32/boards/Passport/graphics/c/Justfile

@ -0,0 +1,9 @@
# SPDX-FileCopyrightText: 2021 Foundation Devices, Inc. <hello@foundationdevices.com>
#
# SPDX-License-Identifier: GPL-3.0-or-later
# Build all C graphics and copy files to main source folders
build:
make
cp firmware_graphics.* ../../
cp bootloader_graphics.* ../../bootloader/

8
ports/stm32/boards/Passport/graphics/py/Justfile

@ -0,0 +1,8 @@
# SPDX-FileCopyrightText: 2021 Foundation Devices, Inc. <hello@foundationdevices.com>
#
# SPDX-License-Identifier: GPL-3.0-or-later
# Build all Python graphics and copy files to main source folder
build:
make
cp graphics.py ../../modules/

31
ports/stm32/boards/Passport/modfoundation.c

@ -205,6 +205,8 @@ const mp_obj_type_t keypad_type = {
/*=============================================================================
* Start of LCD class
*=============================================================================*/
#define FRAMEBUFFER_ADDR_SRAM4 0x38006920 // NOTE: If SRAM4 layout changes, this must change too!
void
lcd_obj_print(const mp_print_t* print, mp_obj_t self_in, mp_print_kind_t kind)
{
@ -224,6 +226,18 @@ lcd_obj_make_new(const mp_obj_type_t* type, size_t n_args, size_t n_kw, const mp
lcd->base.type = &lcd_type;
lcd->spi = &spi_obj[0];
// lcd_init(false);
// If a framebuffer address was passed, save it to a known location
if (n_args == 1) {
// Get the buffer info from the passed in object
mp_buffer_info_t framebuffer_info;
mp_get_buffer_raise(args[0], &framebuffer_info, MP_BUFFER_READ);
// Save the actual address
uint32_t* framebuffer_addr = (uint32_t*)FRAMEBUFFER_ADDR_SRAM4;
*framebuffer_addr = (uint32_t)(framebuffer_info.buf);
}
return MP_OBJ_FROM_PTR(lcd);
}
@ -1276,7 +1290,7 @@ System_validate_firmware_header(mp_obj_t self, mp_obj_t header)
// New header
passport_firmware_header_t* new_fwhdr = (passport_firmware_header_t*)header_info.buf;
mp_obj_t tuple[3];
mp_obj_t tuple[4];
bool is_valid = verify_header(header_info.buf);
@ -1303,7 +1317,10 @@ System_validate_firmware_header(mp_obj_t self, mp_obj_t header)
vstr_add_strn(&vstr, (const char*)new_fwhdr->info.fwdate, strlen((const char*)new_fwhdr->info.fwdate));
tuple[2] = mp_obj_new_str_from_vstr(&mp_type_str, &vstr);
return mp_obj_new_tuple(3, tuple);
// Is this user-signed firmware?
tuple[3] = mp_const_false;
return mp_obj_new_tuple(4, tuple);
}
} else {
// Invalid header
@ -1317,7 +1334,10 @@ System_validate_firmware_header(mp_obj_t self, mp_obj_t header)
vstr_add_strn(&vstr, (const char*)msg, strlen(msg));
tuple[2] = mp_obj_new_str_from_vstr(&mp_type_str, &vstr);
return mp_obj_new_tuple(3, tuple);
// No header = no user signed firmware
tuple[3] = mp_const_false;
return mp_obj_new_tuple(4, tuple);
}
// is_valid
@ -1329,7 +1349,10 @@ System_validate_firmware_header(mp_obj_t self, mp_obj_t header)
// No error message
tuple[2] = mp_const_none;
return mp_obj_new_tuple(3, tuple);
// Is this user-signed firmware?
tuple[3] = (new_fwhdr->signature.pubkey1 == FW_USER_KEY) ? mp_const_true : mp_const_false;
return mp_obj_new_tuple(4, tuple);
}
/// def System_set_user_firmware_pubkey(self, pubkey) -> None

85
ports/stm32/boards/Passport/modules/actions.py

@ -23,7 +23,7 @@ from common import settings, system, noise, dis
from utils import (UXStateMachine, imported, pretty_short_delay, xfp2str, to_str,
truncate_string_to_width, set_next_addr, scan_for_address, get_accounts, run_chooser,
make_account_name_num, is_valid_address, save_next_addr, needs_microsd, format_btc_address,
is_all_zero, bytes_to_hex_str, split_to_lines)
is_all_zero, bytes_to_hex_str, split_to_lines, is_valid_btc_address, do_address_verify, run_chooser)
from wallets.utils import get_export_mode, get_addr_type_from_address, get_deriv_path_from_addr_type_and_acct
from ux import (the_ux, ux_confirm, ux_enter_pin,
ux_enter_text, ux_scan_qr_code, ux_shutdown,
@ -268,19 +268,13 @@ class VerifyAddressUX(UXStateMachine):
# Scan the address to be verified - should be a normal QR code
system.turbo(True);
address = await ux_scan_qr_code('Verify Address')
system.turbo(False)
if address == None:
return
# Strip prefix if present
if address[0:8].lower() == 'bitcoin:':
address = address[8:]
if not is_valid_address(address):
result = await ux_show_story('That is not a valid Bitcoin address.', title='Error', left_btn='BACK',
right_btn='SCAN', center=True, center_vertically=True)
if result == 'x':
address, is_valid_btc = await is_valid_btc_address(address)
if is_valid_btc == False:
if not self.goto_prev():
# Nothing to return back to, so we must have skipped one or more steps...we're done
return
continue
@ -290,28 +284,9 @@ class VerifyAddressUX(UXStateMachine):
addr_type = get_addr_type_from_address(address, is_multisig)
deriv_path = get_deriv_path_from_addr_type_and_acct(addr_type, self.acct_num, is_multisig)
# Scan addresses to see if it's valid
addr_idx, is_change = await scan_for_address(self.acct_num, address, addr_type, deriv_path, self.multisig_wallet)
if addr_idx >= 0:
# Remember where to start from next time
save_next_addr(self.acct_num, addr_type, addr_idx, is_change)
address = format_btc_address(address, addr_type)
result = await ux_show_story('''Address Verified!
{}
This is a {} address at index {}.'''.format(address, 'change' if is_change == 1 else 'receive', addr_idx),
title='Verify',
left_btn='BACK',
right_btn='CONTINUE',
center=True,
center_vertically=True)
result = await do_address_verify(self.acct_num, address, addr_type, deriv_path, self.multisig_wallet)
if result == 'x':
if not self.goto_prev():
# Nothing to return back to, so we must have skipped one or more steps...we're done
return
return
else:
# User asked to stop searching
@ -371,12 +346,19 @@ async def update_firmware(*a):
return
# Validate the header
is_valid, version, error_msg = system.validate_firmware_header(header)
is_valid, version, error_msg, is_user_signed = system.validate_firmware_header(header)
if not is_valid:
system.turbo(False)
await ux_show_story('Firmware header is invalid.\n\n{}'.format(error_msg), title='Error', left_btn='BACK', right_btn='OK', center=True, center_vertically=True)
return
if is_user_signed:
pubkey_result, pubkey = read_user_firmware_pubkey()
if not pubkey_result or is_all_zero(pubkey):
system.turbo(False)
await ux_show_story('Install a Developer PubKey before loading non-Foundation firmware.\n\n', title='Error', left_btn='BACK', right_btn='OK', center=True, center_vertically=True)
return
system.turbo(False)
# Give the user a chance to confirm/back out
@ -1146,6 +1128,9 @@ async def sign_tx_from_sd(*a):
import stash
# Let the user know that using Testnet is potentially dangerous
await show_testnet_warning()
if stash.bip39_passphrase:
title = '[%s]' % xfp2str(settings.get('xfp'))
else:
@ -1341,6 +1326,9 @@ async def magic_scan(menu, label, item):
title = item.arg
# Let the user know that using Testnet is potentially dangerous
await show_testnet_warning()
while True:
system.turbo(True)
data = await ux_scan_qr_code(title)
@ -1412,10 +1400,17 @@ async def enter_passphrase(menu, label, item):
from constants import MAX_PASSPHRASE_LENGTH
title = item.arg
passphrase = await ux_enter_text(title, label="Enter a Passphrase", max_length=MAX_PASSPHRASE_LENGTH)
passphrase = await ux_enter_text(title, label="Enter Passphrase", max_length=MAX_PASSPHRASE_LENGTH)
# print("Chosen passphrase = {}".format(passphrase))
# None is passed back when user chose "back"
if passphrase == None:
return
# print("Chosen passphrase = {}".format(passphrase))
if passphrase == '':
if not await ux_confirm('Are you sure you want to clear the passphrase?'):
return
else:
if not await ux_confirm('Are you sure you want to apply the passphrase:\n\n{}'.format(passphrase)):
return
@ -1855,13 +1850,13 @@ async def test_derive_addresses(*a):
chain = chains.current_chain()
addrs = []
path = "m/84'/0'/{account}'/{change}/{idx}"
path = "m/84'/{coin_type}'/{account}'/{change}/{idx}"
system.turbo(True)
start_time = utime.ticks_ms()
with stash.SensitiveValues() as sv:
for idx in range(n):
subpath = path.format(account=0, change=0, idx=idx)
subpath = path.format(coin_type=chain.b44_cointype, account=0, change=0, idx=idx)
node = sv.derive_path(subpath, register=False)
addr = chain.address(node, AF_P2WPKH)
addrs.append(addr)
@ -2083,3 +2078,23 @@ async def remove_user_firmware_pubkey(*a):
center=True,
center_vertically=True)
clear_cached_pubkey()
async def show_testnet_warning():
chain = settings.get('chain', 'BTC')
if chain == 'TBTC':
await ux_show_story('Passport is in Testnet mode. Use a separate seed to avoid issues with malicious software wallets.',
title='Warning',
center=True,
center_vertically=True)
async def testnet_chooser(*a):
from choosers import chain_chooser
old_chain = settings.get('chain', 'BTC')
await run_chooser(chain_chooser, 'Passport', show_checks=True)
new_chain = settings.get('chain', 'BTC')
# Only display the warning if the chain changed
if new_chain != old_chain:
# Let the user know that using Testnet is potentially dangerous
await show_testnet_warning()

8
ports/stm32/boards/Passport/modules/auth.py

@ -356,10 +356,10 @@ class ApproveTransaction(UserAuthorizedAction):
# - expects CTxOut object
# - gives user-visible string
#
val = ''.join(self.chain.render_value(o.nValue))
val, label = self.chain.render_value(o.nValue)
dest = self.chain.render_address(o.scriptPubKey)
return '\n%s\n\nDestination:\n%s' % (val, dest)
return '\n%s %s\n\nDestination:\n%s' % (val, label, dest)
def render_warnings(self):
with uio.StringIO() as msg:
@ -584,9 +584,7 @@ class ApproveTransaction(UserAuthorizedAction):
msg.write('\nNo change')
return msg.getvalue()
total_val = ' '.join(self.chain.render_value(total))
msg.write("\n%s\n" % total_val)
msg.write("\n%s %s\n" % self.chain.render_value(total))
if len(addrs) == 1:
msg.write('\nChange Address:\n%s\n' % addrs[0])

19
ports/stm32/boards/Passport/modules/chains.py

@ -150,9 +150,16 @@ class ChainsBase:
@classmethod
def render_value(cls, val, unpad=False):
# convert nValue from a transaction into human form.
# convert nValue from a transaction into either BTC or sats
# - always be precise
# - return (string, units label)
from common import settings
from constants import UNIT_TYPE_BTC, UNIT_TYPE_SATS
# BTC is the default if not set yet
units = settings.get('units', UNIT_TYPE_BTC)
if units == UNIT_TYPE_BTC:
label = cls.ctype
if unpad:
if (val % 1E8):
# precise but unpadded
@ -163,7 +170,10 @@ class ChainsBase:
else:
# all the zeros
txt = '%d.%08d' % (val // 1E8, val % 1E8)
return txt, cls.ctype
else:
label = cls.ctype_sats
txt = ('{:,}'.format(val))
return txt, label
@classmethod
def render_address(cls, script):
@ -193,8 +203,10 @@ class ChainsBase:
class BitcoinMain(ChainsBase):
# see <https://github.com/bitcoin/bitcoin/blob/master/src/chainparams.cpp#L140>
ctype = 'BTC'
ctype_sats = 'sats'
name = 'Bitcoin'
core_name = 'Bitcoin Core'
menu_name = 'Bitcoin Mainnet'
slip132 = {
AF_CLASSIC: Slip132Version(0x0488B21E, 0x0488ADE4, 'x'),
@ -214,8 +226,9 @@ class BitcoinMain(ChainsBase):
class BitcoinTestnet(BitcoinMain):
ctype = 'TBTC'
ctype_sats = 'tsats'
name = 'Bitcoin Testnet'
menu_name = 'Testnet: BTC'
menu_name = 'Bitcoin Testnet'
slip132 = {
AF_CLASSIC: Slip132Version(0x043587cf, 0x04358394, 't'),

52
ports/stm32/boards/Passport/modules/choosers.py

@ -72,4 +72,56 @@ def enable_passphrase_chooser():
settings.set('enable_passphrase', va[idx])
return which, ch, set_enable_passphrase
def chain_chooser():
from chains import AllChains
chain = settings.get('chain', 'BTC')
ch = [(i.ctype, i.menu_name or i.name) for i in AllChains ]
# find index of current choice
try:
which = [n for n, (k,v) in enumerate(ch) if k == chain][0]
except IndexError:
which = 0
def set_chain(idx, text):
val = ch[idx][0]
assert ch[idx][1] == text
settings.set_volatile('chain', val)
try:
# update xpub stored in settings
import stash
with stash.SensitiveValues() as sv:
sv.capture_xpub()
except ValueError:
# no secrets yet, not an error
pass
return which, [t for _,t in ch], set_chain
def units_chooser():
import chains
from constants import UNIT_TYPE_BTC, UNIT_TYPE_SATS
chain = chains.current_chain()
units = settings.get('units', UNIT_TYPE_BTC)
ch = [chain.ctype,
chain.ctype_sats]
val = [UNIT_TYPE_BTC,
UNIT_TYPE_SATS]
try:
which = val.index(units)
except ValueError:
which = UNIT_TYPE_BTC
def set_units(idx, text):
settings.set('units', val[idx])
return which, ch, set_units
# EOF

7
ports/stm32/boards/Passport/modules/constants.py

@ -39,3 +39,10 @@ MAX_ACCOUNT_NAME_LEN = 20
MAX_MULTISIG_NAME_LEN = 20
DEFAULT_ACCOUNT_ENTRY = {'name': 'Primary', 'acct_num': 0}
# Unit types for labeling conversions
UNIT_TYPE_BTC = 0
UNIT_TYPE_SATS = 1
# Maximum amount of characters in a text entry screen
MAX_MESSAGE_LEN = 64

36
ports/stm32/boards/Passport/modules/display.py

@ -42,11 +42,11 @@ class Display:
def __init__(self):
# Setup frame buffer, in show we will call scrn.update(self.dis) to show the buffer
self.scrn = LCD()
self.dis = framebuf.FrameBuffer(bytearray(
self.LINE_SIZE_BYTES * self.HEIGHT), self.FB_WIDTH, self.HEIGHT, framebuf.MONO_HLSB)
self.scrn = LCD(self.dis)
self.backlight = Backlight()
self.clear()
@ -192,27 +192,31 @@ class Display:
fn = lookup(font, ord(ch))
return fn.advance
def text_input(self, x, y, msg, font=FontSmall, invert=0, cursor_pos=None, visible_spaces=False, fixed_spacing=None, cursor_shape='line', max_chars_per_line=0):
if max_chars_per_line > 0:
# TODO: Improve this by splitting lines based on actual pixel widths instead of max_chars_per_line
# Split text into multiple lines and draw them separately
lines = [msg[i:i+max_chars_per_line]
for i in range(0, len(msg), max_chars_per_line)]
def text_input(self, x, y, msg, font=FontSmall, invert=0, cursor_pos=None, visible_spaces=False, fixed_spacing=None, cursor_shape='line'):
from ux import word_wrap
from utils import split_by_char_size
from constants import MAX_MESSAGE_LEN
# Maximum message size is MAX_MESSAGE_LEN (64) characters
if len(msg) >= MAX_MESSAGE_LEN:
msg = msg[:MAX_MESSAGE_LEN]
if hasattr(msg, 'readline'):
lines = split_by_char_size(msg.getvalue(), font)
else:
lines = split_by_char_size(msg, font)
# Special case to draw cursor by itself when no text is entered yet
if len(lines) == 0:
if len(msg) == 0:
self.text(x, y, '', font, invert, cursor_pos,
visible_spaces, fixed_spacing, cursor_shape)
else:
for line in lines:
self.text(x, y, line, font, invert, cursor_pos,
visible_spaces, fixed_spacing, cursor_shape)
y += font.leading
cursor_pos -= max_chars_per_line
else:
self.text(x, y, msg, font, invert, cursor_pos,
visible_spaces, fixed_spacing, cursor_shape)
visible_spaces, fixed_spacing, cursor_shape, True)
# move the y down enough to make room for 7 lines of text (hence the -2)
y += font.leading - 2
cursor_pos -= len(line)
def text(self, x, y, msg, font=FontSmall, invert=0, cursor_pos=None, visible_spaces=False, fixed_spacing=None, cursor_shape='line', scrollbar_visible=False):
# Draw at x,y (top left corner of first letter)

12
ports/stm32/boards/Passport/modules/export.py

@ -484,8 +484,9 @@ async def write_complete_backup(words, auto_backup=False, is_first_backup=False)
body = render_backup_contents().encode()
backup_num = settings.get('backup_num', 1)
# print('backup_num={}'.format(backup_num))
backup_num = 1
xfp = xfp2str(settings.get('xfp')).lower()
# print('XFP: {}'.format(xfp))
gc.collect()
@ -520,7 +521,7 @@ async def write_complete_backup(words, auto_backup=False, is_first_backup=False)
# Make a unique filename
while True:
base_filename = 'passport-backup-{}.7z'.format(backup_num)
base_filename = '{}-backup-{}.7z'.format(xfp, backup_num)
fname = '{}/{}'.format(backups_path, base_filename)
# Ensure filename doesn't already exist
@ -529,7 +530,6 @@ async def write_complete_backup(words, auto_backup=False, is_first_backup=False)
# Ooops...that exists, so increment and try again
backup_num += 1
# print('backup_num={}'.format(backup_num))
# print('Saving to fname={}'.format(fname))
@ -558,10 +558,6 @@ async def write_complete_backup(words, auto_backup=False, is_first_backup=False)
else:
return
# Update backup counter
backup_num += 1
settings.set('backup_num', backup_num)
if not auto_backup:
await ux_show_story('Saved backup to\n\n{}\n\nin /backups folder.'.format(base_filename),
title='Success', left_btn='NEXT', center=True, center_vertically=True)

11
ports/stm32/boards/Passport/modules/flow.py

@ -19,7 +19,7 @@ from public_constants import AF_P2WPKH
from multisig import make_multisig_menu
from wallets.utils import has_export_mode
from export import view_backup_password
from utils import is_new_wallet_in_progress, get_accounts, is_screenshot_mode_enabled
from utils import is_new_wallet_in_progress, get_accounts, is_screenshot_mode_enabled, run_chooser
from new_wallet import pair_new_wallet
from ie import show_browser
@ -52,6 +52,11 @@ def has_pubkey():
return False
return not is_all_zero(common.cached_pubkey)
PassphraseMenu = [
MenuItem('Set Passphrase', f=enter_passphrase, arg='Passphrase'),
MenuItem('Enter at Startup', menu_title='Passphrase', chooser=enable_passphrase_chooser)
]
DeveloperPubkeyMenu = [
MenuItem('Install PubKey', predicate=lambda: not has_pubkey(), f=install_user_firmware_pubkey),
MenuItem('View PubKey', predicate=has_pubkey, f=view_user_firmware_pubkey),
@ -60,11 +65,13 @@ DeveloperPubkeyMenu = [
AdvancedMenu = [
MenuItem('Change PIN', f=change_pin),
MenuItem('Passphrase', menu_title='Passphrase', chooser=enable_passphrase_chooser),
MenuItem('Units', chooser=units_chooser),
MenuItem('Passphrase', menu_title='Passphrase', menu=PassphraseMenu),
MenuItem('Sign Text File', predicate=has_secrets, f=sign_message_on_sd),
MenuItem('MicroSD Settings', menu=SDCardMenu),
MenuItem('View Seed Words', f=view_seed_words, predicate=lambda: settings.get('words', True)),
MenuItem('Developer PubKey', menu=DeveloperPubkeyMenu, menu_title='Developer'),
MenuItem('Testnet', f=testnet_chooser),
MenuItem('Erase Passport', f=erase_wallet, arg=True)
]

1
ports/stm32/boards/Passport/modules/menu.py

@ -277,6 +277,7 @@ class MenuSystem:
ch = self.items[idx]
await ch.activate(self, idx)
self.input.reset()
async def interact(self):
# Only public entry point: do stuff.

4
ports/stm32/boards/Passport/modules/multisig.py

@ -981,7 +981,7 @@ class MultisigWallet:
@classmethod
def import_from_psbt(cls, M, N, xpubs_list):
# given the raw data fro PSBT global header, offer the user
# given the raw data for PSBT global header, offer the user
# the details, and/or bypass that all and just trust the data.
# - xpubs_list is a list of (xfp+path, binary BIP32 xpub)
# - already know not in our records.
@ -1017,7 +1017,7 @@ class MultisigWallet:
assert has_mine == 1 # 'my key not included'
name = 'PSBT-%d-of-%d' % (M, N)
ms = cls(name, (M, N), xpubs, chain_type=expect_chain, addr_fmt=addr_fmt or AF_P2SH)
ms = cls(name, (M, N), xpubs, name, chain_type=expect_chain, addr_fmt=addr_fmt or AF_P2SH)
# may just keep just in-memory version, no approval required, if we are
# trusting PSBT's today, otherwise caller will need to handle UX w.r.t new wallet

87
ports/stm32/boards/Passport/modules/new_wallet.py

@ -25,7 +25,9 @@ from utils import (
save_next_addr,
make_account_name_num,
get_accounts,
format_btc_address)
format_btc_address,
is_valid_btc_address,
do_address_verify)
from wallets.constants import *
from uasyncio import sleep_ms
from constants import DEFAULT_ACCOUNT_ENTRY
@ -389,6 +391,17 @@ class NewWalletUX(UXStateMachine):
elif method == 'show_addresses':
self.goto(self.SHOW_RX_ADDRESSES_VERIFICATION_INTRO, save_curr=save_curr)
def choose_multisig_import_mode(self):
if 'mulitsig_import_mode' in self.export_mode:
if self.export_mode['mulitsig_import_mode'] == EXPORT_MODE_QR:
self.goto(self.IMPORT_MULTISIG_CONFIG_FROM_QR, save_curr=False)
else:
self.goto(self.IMPORT_MULTISIG_CONFIG_FROM_MICROSD, save_curr=False)
elif self.export_mode['id'] == EXPORT_MODE_QR:
self.goto(self.IMPORT_MULTISIG_CONFIG_FROM_QR, save_curr=False)
else:
self.goto(self.IMPORT_MULTISIG_CONFIG_FROM_MICROSD, save_curr=False)
async def show(self):
while True:
# print('show: state={}'.format(self.state))
@ -539,23 +552,29 @@ class NewWalletUX(UXStateMachine):
# If multisig, we need to import the quorum/config info first, else go right to validating the first
# receive address from the wallet.
if self.is_multisig():
self.goto(self.IMPORT_MULTISIG_CONFIG_FROM_QR, save_curr=False)
self.choose_multisig_import_mode()
else:
self.goto_address_verification_method(save_curr=False)
elif self.state == self.EXPORT_TO_MICROSD:
from files import CardSlot
from utils import xfp2str
data = self.prepare_to_export()
data_hash = bytearray(32)
system.sha256(data, data_hash)
fname = ''
# Write the data to SD with the filename the wallet prefers
filename_pattern = self.export_mode['filename_pattern_multisig'] if self.is_multisig() else self.export_mode['filename_pattern']
try:
with CardSlot() as card:
# Make a filename with the option of injecting the sd path, hash of the data, acct num, random number
fname = filename_pattern.format(sd=card.get_sd_root(), hash=data_hash, acct=self.acct_num, random=random_hex(8))
fname = filename_pattern.format(sd=card.get_sd_root(),
hash=data_hash,
acct=self.acct_num,
random=random_hex(8),
xfp=xfp2str(settings.get('xfp')).lower())
# print('Saving to fname={}'.format(fname))
# Write the data
@ -579,17 +598,21 @@ class NewWalletUX(UXStateMachine):
self.exported = True
self.save_new_wallet_progress()
dis.fullscreen('Saved to microSD')
base_filename = fname.split(card.get_sd_root() + '/', 1)[1]
result = await ux_show_story('Saved file to your microSD card.\n{}'.format(base_filename),
title='Success',
left_btn='NEXT',
center=True,
center_vertically=True)
await sleep_ms(1000)
# If multisig, we need to import the quorum/config info first, else go right to validating the first
# receive address from the wallet.
if self.is_multisig():
self.goto(self.IMPORT_MULTISIG_CONFIG_FROM_MICROSD, save_curr=False)
self.choose_multisig_import_mode()
else:
self.goto_address_verification_method(save_curr=False)
elif self.state == self.IMPORT_MULTISIG_CONFIG_FROM_QR:
while True:
msg = self.get_custom_text('multisig_import_qr', 'Next, import the multisig configuration from {} via QR code.'.format(self.sw_wallet['label']))
@ -682,8 +705,9 @@ Generate a new receive address in {} and scan the QR code on the next page.'''.f
elif self.state == self.SCAN_RX_ADDRESS:
# Scan the address to be verified - should be a normal QR code
system.turbo(True);
system.turbo(True)
address = await ux_scan_qr_code('Verify Address')
system.turbo(False)
if address == None:
# User backed out without scanning an address
@ -699,14 +723,8 @@ Generate a new receive address in {} and scan the QR code on the next page.'''.f
self.goto_prev()
continue
# Strip prefix if present
if address[0:8].lower() == 'bitcoin:':
address = address[8:]
if not is_valid_address(address):
result = await ux_show_story('That is not a valid Bitcoin address.', title='Error', left_btn='BACK',
right_btn='SCAN', center=True, center_vertically=True)
if result == 'x':
address, is_valid_btc = await is_valid_btc_address(address)
if is_valid_btc == False:
if not self.goto_prev():
return
continue
@ -714,39 +732,19 @@ Generate a new receive address in {} and scan the QR code on the next page.'''.f
# Use address to nail down deriv_path and addr_type, if not yet known
self.infer_wallet_info(address=address)
# Scan addresses to see if it's valid
addr_idx, is_change = await scan_for_address(self.acct_num, address, self.addr_type, self.deriv_path, self.multisig_wallet)
if addr_idx >= 0:
# Found it!
self.verified = True
# Remember where to start from next time
save_next_addr(self.acct_num, self.addr_type, addr_idx, is_change)
address = format_btc_address(address, self.addr_type)
result = await ux_show_story('''Address Verified!
{}
This is a {} address at index {}.'''.format(address, 'change' if is_change == 1 else 'receive', addr_idx),
title='Verify',
left_btn='BACK',
right_btn='CONTINUE',
center=True,
center_vertically=True)
if result == 'x':
if not self.goto_prev():
# Nothing to return back to, so we must have skipped one or more steps...were' done
return
self.goto(self.CONFIRMATION)
continue
else:
result = do_address_verify(self.acct_num, address, self.addr_type, self.deriv_path, self.multisig_wallet)
if result == False:
result = await ux_show_story('Do you want to SKIP address verification or SCAN another address?', title='Not Found', left_btn='SKIP',
right_btn='SCAN', center=True, center_vertically=True)
if result == 'x':
# Skipping address scan
self.infer_wallet_info(ms_wallet=self.multisig_wallet)
self.goto(self.CONFIRMATION)
else:
# Address was found!
self.verified = True
self.goto(self.CONFIRMATION)
continue
# else loop around and scan again
@ -843,10 +841,7 @@ Compare them with the addresses shown on the next screen to make sure they match
if self.is_multisig():
if not self.multisig_wallet:
# Need to import the multisig wallet
if self.export_mode['id'] == 'qr':
self.goto(self.IMPORT_MULTISIG_CONFIG_FROM_QR)
else:
self.goto(self.IMPORT_MULTISIG_CONFIG_FROM_MICROSD)
self.choose_multisig_import_mode()
continue
if not self.verified:

10
ports/stm32/boards/Passport/modules/schema_evolution.py

@ -29,6 +29,16 @@ async def handle_schema_evolutions(update_from_to):
from_version = to_version
continue
elif from_version == '1.0.6' and to_version == '1.0.7':
# Handle evolutions
# This no longer used, so clean it out
settings.remove('backup_num')
# This is now volatile, so clean it out
settings.remove('chain')
from_version = to_version
continue
# We only reach here if no more evolutions are possible.
# Remove the update indicator from the settings.
# NOTE: There is a race condition here, but these evolutions should be extremely fast, and ideally

2
ports/stm32/boards/Passport/modules/sram4.py

@ -36,5 +36,7 @@ flash_cache_buf = _alloc(16 * 1024)
tmp_buf = _alloc(1024)
psbt_tmp256 = _alloc(256)
viewfinder_buf = _alloc((VIEWFINDER_WIDTH*VIEWFINDER_HEIGHT) // 8)
framebuffer_addr = _alloc(4) # Address of the framebuffer memory so we can read it from OCD
assert _start <= SRAM4_END

2
ports/stm32/boards/Passport/modules/stash.py

@ -221,7 +221,7 @@ class SensitiveValues:
settings.set_volatile('xfp', xfp)
settings.set_volatile('xpub', xpub)
settings.set('chain', self.chain.ctype)
settings.set_volatile('chain', self.chain.ctype)
settings.set('words', (self.mode == 'words'))
def register(self, item):

76
ports/stm32/boards/Passport/modules/utils.py

@ -446,7 +446,15 @@ async def show_top_menu():
# TODO: For now this just checks the front bytes, but it could ensure the whole thing is valid
def is_valid_address(address):
return (len(address) > 3) and (address[0] == '1' or address[0] == '3' or (address[0] == 'b' and address[1] == 'c' and address[2] == '1'))
# Valid addresses: 1 , 3 , bc1, tb1, m, n, 2
return (len(address) > 3) and \
((address[0] == '1') or \
(address[0] == '2') or \
(address[0] == '3') or \
(address[0] == 'm') or \
(address[0] == 'n') or \
(address[0] == 'b' and address[1] == 'c' and address[2] == '1') or \
(address[0] == 't' and address[1] == 'b' and address[2] == '1'))
# Return array of bytewords where each byte in buf maps to a word
@ -491,10 +499,14 @@ def ensure_folder_exists(path):
return
def file_exists(path):
import os
from stat import S_ISREG
try:
with open(fname, 'wb') as fd:
return True
except:
s = os.stat(path)
mode = s[0]
return S_ISREG(mode)
except OSError as e:
return False
def folder_exists(path):
@ -691,6 +703,48 @@ async def scan_for_address(acct_num, address, addr_type, deriv_path, ms_wallet):
if result == 'x':
return -1, False
async def is_valid_btc_address(address):
from ux import ux_show_story
# Strip prefix if present
if address[0:8].lower() == 'bitcoin:':
address = address[8:]
if not is_valid_address(address):
await ux_show_story('That is not a valid Bitcoin address.', title='Error', left_btn='BACK',
right_btn='SCAN', center=True, center_vertically=True)
return address, False
else:
return address, True
async def do_address_verify(acct_num, address, addr_type, deriv_path, multisig_wallet):
from common import system
from ux import ux_show_story
system.turbo(True)
# Scan addresses to see if it's valid
addr_idx, is_change = await scan_for_address(acct_num, address, addr_type, deriv_path, multisig_wallet)
if addr_idx >= 0:
# Remember where to start from next time
save_next_addr(acct_num, addr_type, addr_idx, is_change)
address = format_btc_address(address, addr_type)
result = await ux_show_story('''Address Verified!
{}
This is a {} address at index {}.'''.format(address, 'change' if is_change == 1 else 'receive', addr_idx),
title='Verify',
left_btn='BACK',
right_btn='CONTINUE',
center=True,
center_vertically=True)
system.turbo(False)
return True
else:
system.turbo(False)
return
def is_new_wallet_in_progress():
from common import settings
ap = settings.get('wallet_prog', None)
@ -835,4 +889,18 @@ def is_all_zero(buf):
def split_to_lines(s, width):
return '\n'.join([s[i:i+width] for i in range(0, len(s), width)])
def split_by_char_size(msg, font):
from display import Display
from ux import MAX_WIDTH, word_wrap
from common import dis
lines = []
for ln in msg.split('\n'):
if dis.width(ln, font) > MAX_WIDTH:
lines.extend(word_wrap(ln, font))
else:
# ok if empty string, just a blank line
lines.append(ln)
return lines
# EOF

41
ports/stm32/boards/Passport/modules/ux.py

@ -26,6 +26,8 @@ LEFT_MARGIN = 8
RIGHT_MARGIN = 6
TOP_MARGIN = 12
VERT_SPACING = 10
MAX_WIDTH = Display.WIDTH - LEFT_MARGIN - \
RIGHT_MARGIN - Display.SCROLLBAR_WIDTH
TEXTBOX_MARGIN = 6
@ -168,6 +170,13 @@ class KeyInputHandler:
from common import keypad
keypad.clear_keys()
# Reset internal state so that all pending kcodes and repeats are forgotten.
def reset(self):
self.time_pressed = {}
self.kcode_state = 0
self.kcode_last_time_pressed = 0
self.repeat_active = False
# New input function to be used in place of PressRelease and ux_press_release, ux_all_up and ux_poll_once.
async def get_event(self):
from common import keypad
@ -455,7 +464,7 @@ async def ux_enter_text(title="Enter Text", label="Text", initial_text='', left_
# Draw the text and any other stuff
y += 4
dis.text_input(None, y, text_handler.get_text(),
cursor_pos=text_handler.cursor_pos, font=font, max_chars_per_line=14)
cursor_pos=text_handler.cursor_pos, font=font)
dis.draw_footer(left_btn, right_btn, input.is_pressed(
'x'), input.is_pressed('y'))
@ -587,7 +596,6 @@ async def ux_show_symbols_popup(title="Enter Passphrase"):
def chars_per_line(font):
return (Display.WIDTH - LEFT_MARGIN - Display.SCROLLBAR_WIDTH) // font.advance
def word_wrap(ln, font):
from common import dis
max_width = Display.WIDTH - LEFT_MARGIN - \
@ -626,6 +634,7 @@ async def ux_show_story(msg, title='Passport', sensitive=False, font=FontSmall,
right_btn='CONTINUE', scroll_label=None, left_btn_enabled=True, right_btn_enabled=True,
center_vertically=False, center=False, overlay=None, clear_keys=False):
from common import dis, keypad
from utils import split_by_char_size
system.turbo(True)
@ -633,34 +642,10 @@ async def ux_show_story(msg, title='Passport', sensitive=False, font=FontSmall,
if clear_keys:
keypad.clear_keys()
ch_per_line = chars_per_line(font)
lines = []
# First case is used with StringIO objects
if hasattr(msg, 'readline'):
msg.seek(0)
for ln in msg:
if ln[-1] == '\n':
ln = ln[:-1]
if len(ln) > ch_per_line:
lines.extend(word_wrap(ln, font))
else:
# ok if empty string, just a blank line
lines.append(ln)
# no longer needed & rude to our caller, but let's save the memory
msg.close()
del msg
gc.collect()
lines = split_by_char_size(msg.getvalue(), font)
else:
for ln in msg.split('\n'):
if len(ln) > ch_per_line:
lines.extend(word_wrap(ln, font))
else:
# ok if empty string, just a blank line
lines.append(ln)
lines = split_by_char_size(msg, font)
# trim blank lines at end
while not lines[-1]:

4
ports/stm32/boards/Passport/modules/wallets/bitcoin_core.py

@ -102,7 +102,7 @@ BitcoinCoreWallet = {
{'id':'single-sig', 'label':'Single-sig', 'addr_type': None, 'create_wallet': create_bitcoin_core_export},
],
'export_modes': [
{'id': 'microsd', 'label': 'microSD', 'filename_pattern': '{sd}/passport-bitcoin-core.txt', 'ext': '.txt',
'filename_pattern_multisig': '{sd}/passport-bitcoin-core-multisig.json'}
{'id': 'microsd', 'label': 'microSD', 'filename_pattern': '{sd}/{xfp}-bitcoin-core.txt', 'ext': '.txt',
'filename_pattern_multisig': '{sd}/{xfp}-bitcoin-core-multisig.json'}
]
}

2
ports/stm32/boards/Passport/modules/wallets/bluewallet.py

@ -19,6 +19,6 @@ BlueWallet = {
],
'export_modes': [
{'id': 'qr', 'label': 'QR Code', 'qr_type': QRType.UR1},
{'id': 'microsd', 'label': 'microSD', 'filename_pattern': '{sd}/passport-bluewallet.json', 'filename_pattern_multisig': '{sd}/passport-bluewallet-multisig.json'}
{'id': 'microsd', 'label': 'microSD', 'filename_pattern': '{sd}/{xfp}-bluewallet.json', 'filename_pattern_multisig': '{sd}/{xfp}-bluewallet-multisig.json'}
]
}

2
ports/stm32/boards/Passport/modules/wallets/btcpay.py

@ -18,6 +18,6 @@ BtcPayWallet = {
'address_validation_method': 'show_addresses',
'export_modes': [
{'id': 'qr', 'label': 'QR Code', 'qr_type': QRType.QR},
{'id': 'microsd', 'label': 'microSD', 'filename_pattern': '{sd}/passport-btcpay.json', 'filename_pattern_multisig': '{sd}/passport-btcpay-multisig.json'}
{'id': 'microsd', 'label': 'microSD', 'filename_pattern': '{sd}/{xfp}-btcpay.json', 'filename_pattern_multisig': '{sd}/{xfp}-btcpay-multisig.json'}
]
}

2
ports/stm32/boards/Passport/modules/wallets/caravan.py

@ -14,6 +14,6 @@ CaravanWallet = {
'import_microsd': read_multisig_config_from_microsd}
],
'export_modes': [
{'id': 'microsd', 'label': 'microSD', 'filename_pattern': '{sd}/passport-caravan.json', 'filename_pattern_multisig': '{sd}/passport-caravan-multisig.json'}
{'id': 'microsd', 'label': 'microSD', 'filename_pattern': '{sd}/{xfp}-caravan.json', 'filename_pattern_multisig': '{sd}/{xfp}-caravan-multisig.json'}
]
}

4
ports/stm32/boards/Passport/modules/wallets/casa.py

@ -54,7 +54,7 @@ CasaWallet = {
'import_microsd': read_multisig_config_from_microsd}
],
'export_modes': [
{'id': 'microsd', 'label': 'microSD', 'filename_pattern': '{sd}/passport-casa.txt', 'ext': '.txt',
'filename_pattern_multisig': '{sd}/passport-casa-multisig.json', 'ext_multisig': '.txt',}
{'id': 'microsd', 'label': 'microSD', 'filename_pattern': '{sd}/{xfp}-casa.txt', 'ext': '.txt',
'filename_pattern_multisig': '{sd}/{xfp}-casa-multisig.txt', 'ext_multisig': '.txt'}
]
}

2
ports/stm32/boards/Passport/modules/wallets/dux_reserve.py

@ -18,6 +18,6 @@ DuxReserveWallet = {
'import_microsd': read_multisig_config_from_microsd}
],
'export_modes': [
{'id': 'microsd', 'label': 'microSD', 'filename_pattern': '{sd}/passport-dux.json', 'filename_pattern_multisig': '{sd}/passport-dux-multisig.json'}
{'id': 'microsd', 'label': 'microSD', 'filename_pattern': '{sd}/{xfp}-dux.json', 'filename_pattern_multisig': '{sd}/{xfp}-dux-multisig.json'}
]
}

2
ports/stm32/boards/Passport/modules/wallets/electrum.py

@ -136,6 +136,6 @@ ElectrumWallet = {
# 'import_microsd': read_multisig_config_from_microsd}
],
'export_modes': [
{'id': 'microsd', 'label': 'microSD', 'filename_pattern': '{sd}/passport-electrum.json', 'filename_pattern_multisig': '{sd}/passport-electrum-multisig.json'}
{'id': 'microsd', 'label': 'microSD', 'filename_pattern': '{sd}/{xfp}-electrum.json', 'filename_pattern_multisig': '{sd}/{xfp}-electrum-multisig.json'}
]
}

2
ports/stm32/boards/Passport/modules/wallets/fullynoded.py

@ -19,6 +19,6 @@ FullyNodedWallet = {
],
'export_modes': [
{'id': 'qr', 'label': 'QR Code', 'qr_type': QRType.UR2},
{'id': 'microsd', 'label': 'microSD', 'filename_pattern': '{sd}/passport-fullynoded.json', 'filename_pattern_multisig': '{sd}/passport-fullynoded-multisig.json'}
{'id': 'microsd', 'label': 'microSD', 'filename_pattern': '{sd}/{xfp}-fullynoded.json', 'filename_pattern_multisig': '{sd}/{xfp}-fullynoded-multisig.json'}
]
}

12
ports/stm32/boards/Passport/modules/wallets/generic_json_wallet.py

@ -32,13 +32,13 @@ def create_generic_json_wallet(sw_wallet=None, addr_type=None, acct_num=0, multi
with stash.SensitiveValues() as sv:
# Each of these paths will have /{change}/{idx} in usage (not hardened)
for name, deriv, fmt, atype, is_multisig in [
( 'bip44', "m/44'/0'/{acct}'", AF_CLASSIC, 'p2pkh', False ),
( 'bip49', "m/49'/0'/{acct}'", AF_P2WPKH_P2SH, 'p2sh-p2wpkh', False ), # was "p2wpkh-p2sh"
( 'bip84', "m/84'/0'/{acct}'", AF_P2WPKH, 'p2wpkh', False ),
( 'bip48_1', "m/48'/0'/{acct}'/1'", AF_P2WSH_P2SH, 'p2sh-p2wsh', True ),
( 'bip48_2', "m/48'/0'/{acct}'/2'", AF_P2WSH, 'p2wsh', True ),
( 'bip44', "m/44'/{coin_type}'/{acct}'", AF_CLASSIC, 'p2pkh', False ),
( 'bip49', "m/49'/{coin_type}'/{acct}'", AF_P2WPKH_P2SH, 'p2sh-p2wpkh', False ), # was "p2wpkh-p2sh"
( 'bip84', "m/84'/{coin_type}'/{acct}'", AF_P2WPKH, 'p2wpkh', False ),
( 'bip48_1', "m/48'/{coin_type}'/{acct}'/1'", AF_P2WSH_P2SH, 'p2sh-p2wsh', True ),
( 'bip48_2', "m/48'/{coin_type}'/{acct}'/2'", AF_P2WSH, 'p2wsh', True ),
]:
dd = deriv.format(acct=acct_num)
dd = deriv.format(coin_type=chain.b44_cointype,acct=acct_num)
node = sv.derive_path(dd)
xfp = xfp2str(node.my_fingerprint())
xp = chain.serialize_public(node, AF_CLASSIC)

2
ports/stm32/boards/Passport/modules/wallets/gordian.py

@ -19,6 +19,6 @@ GordianWallet = {
],
'export_modes': [
{'id': 'qr', 'label': 'QR Code', 'qr_type': QRType.UR2},
{'id': 'microsd', 'label': 'microSD', 'filename_pattern': '{sd}/passport-gordian.json', 'filename_pattern_multisig': '{sd}/passport-gordian-multisig.json'}
{'id': 'microsd', 'label': 'microSD', 'filename_pattern': '{sd}/{xfp}-gordian.json', 'filename_pattern_multisig': '{sd}/{xfp}-gordian-multisig.json'}
]
}

2
ports/stm32/boards/Passport/modules/wallets/lily.py

@ -20,6 +20,6 @@ LilyWallet = {
],
'export_modes': [
# {'id': 'qr', 'label': 'QR Code', 'qr_type': QRType.UR1},
{'id': 'microsd', 'label': 'microSD', 'filename_pattern': '{sd}/passport-lily.json', 'filename_pattern_multisig': '{sd}/passport-lily-multisig.json'}
{'id': 'microsd', 'label': 'microSD', 'filename_pattern': '{sd}/{xfp}-lily.json', 'filename_pattern_multisig': '{sd}/{xfp}-lily-multisig.json'}
]
}

8
ports/stm32/boards/Passport/modules/wallets/multisig_json.py

@ -10,6 +10,7 @@
# multisig_json.py - Multisig export format
#
import chains
import stash
import uio
from utils import xfp2str
@ -20,6 +21,7 @@ from public_constants import AF_P2SH, AF_P2WSH, AF_P2WSH_P2SH
def create_multisig_json_wallet(sw_wallet=None, addr_type=None, acct_num=0, multisig=False, legacy=False):
fp = uio.StringIO()
chain = chains.current_chain()
fp.write('{\n')
accts = []
@ -27,11 +29,11 @@ def create_multisig_json_wallet(sw_wallet=None, addr_type=None, acct_num=0, mult
for deriv, name, fmt in [
("m/45'", 'p2sh', AF_P2SH),
("m/48'/0'/{acct}'/1'", 'p2wsh_p2sh', AF_P2WSH_P2SH),
("m/48'/0'/{acct}'/2'", 'p2wsh', AF_P2WSH)
("m/48'/{coin_type}'/{acct}'/1'", 'p2wsh_p2sh', AF_P2WSH_P2SH),
("m/48'/{coin_type}'/{acct}'/2'", 'p2wsh', AF_P2WSH)
]:
# Fill in the acct number
dd = deriv.format(acct=acct_num)
dd = deriv.format(coin_type=chain.b44_cointype,acct=acct_num)
node = sv.derive_path(dd)
xfp = xfp2str(node.my_fingerprint())
xpub = sv.chain.serialize_public(node, fmt)

2
ports/stm32/boards/Passport/modules/wallets/sparrow.py

@ -18,6 +18,6 @@ SparrowWallet = {
],
'export_modes': [
{'id': 'qr', 'label': 'QR Code', 'qr_type': QRType.UR2},
{'id': 'microsd', 'label': 'microSD', 'filename_pattern': '{sd}/passport-sparrow.json', 'filename_pattern_multisig': '{sd}/passport-sparrow-multisig.json'}
{'id': 'microsd', 'label': 'microSD', 'filename_pattern': '{sd}/{xfp}-sparrow.json', 'filename_pattern_multisig': '{sd}/{xfp}-sparrow-multisig.json'}
]
}

4
ports/stm32/boards/Passport/modules/wallets/specter.py

@ -18,7 +18,7 @@ SpecterWallet = {
'import_qr': read_multisig_config_from_qr, 'import_microsd': read_multisig_config_from_microsd}
],
'export_modes': [
# {'id': 'qr', 'label': 'QR Code', 'qr_type': QRType.UR1},
{'id': 'microsd', 'label': 'microSD', 'filename_pattern': '{sd}/passport-specter.json', 'filename_pattern_multisig': '{sd}/passport-specter-multisig.json'}
{'id': 'qr', 'label': 'QR Code', 'qr_type': QRType.UR2},
{'id': 'microsd', 'label': 'microSD', 'filename_pattern': '{sd}/{xfp}-specter.json', 'filename_pattern_multisig': '{sd}/{xfp}-specter-multisig.json', 'mulitsig_import_mode': 'qr'}
]
}

2
ports/stm32/boards/Passport/modules/wallets/sw_wallets.py

@ -32,6 +32,6 @@ supported_software_wallets = [
# GordianWallet,
# LilyWallet,
SparrowWallet,
# SpecterWallet,
SpecterWallet,
WasabiWallet,
]

34
ports/stm32/boards/Passport/modules/wallets/utils.py

@ -4,6 +4,7 @@
# utils.py - Wallet utils
#
import chains
import common
from common import settings
from public_constants import AF_CLASSIC, AF_P2SH, AF_P2WPKH_P2SH, AF_P2WSH_P2SH, AF_P2WPKH, AF_P2WSH
@ -43,11 +44,12 @@ def get_addr_type_from_address(address, is_multisig):
if len(address) < 26:
return None
if address[0] == '1':
if address[0] == '1' or address[0] == 'm' or address[0] == 'n' :
return AF_P2SH if is_multisig else AF_CLASSIC
elif address[0] == '3':
elif address[0] == '3' or address[0] == '2' :
return AF_P2WSH_P2SH if is_multisig else AF_P2WPKH_P2SH
elif address[0] == 'b' and address[1] == 'c' and address[2] == '1':
elif (address[0] == 'b' and address[1] == 'c' and address[2] == '1') or \
(address[0] == 't' and address[1] == 'b' and address[2] == '1'):
return AF_P2WSH if is_multisig else AF_P2WPKH
return None
@ -94,16 +96,16 @@ def get_deriv_fmt_from_address(address, is_multisig):
# Map the address prefix to a standard derivation path and insert the account number
if is_multisig:
if address[0] == '3':
return "m/48'/0'/{acct}'/1'"
return "m/48'/{coin_type}'/{acct}'/1'"
elif address[0] == 'b' and address[1] == 'c' and address[2] == '1':
return "m/48'/0'/{acct}'/2'"
return "m/48'/{coin_type}'/{acct}'/2'"
else:
if address[0] == '1':
return "m/44'/0'/{acct}'"
return "m/44'/{coin_type}'/{acct}'"
elif address[0] == '3':
return "m/49'/0'/{acct}'"
return "m/49'/{coin_type}'/{acct}'"
elif address[0] == 'b' and address[1] == 'c' and address[2] == '1':
return "m/84'/0'/{acct}'"
return "m/84'/{coin_type}'/{acct}'"
return None
@ -113,33 +115,35 @@ def get_deriv_fmt_from_addr_type(addr_type, is_multisig):
# Map the address prefix to a standard derivation path and insert the account number
if is_multisig:
if addr_type == AF_P2WSH_P2SH:
return "m/48'/0'/{acct}'/1'"
return "m/48'/{coin_type}'/{acct}'/1'"
elif addr_type == AF_P2WSH:
return "m/48'/0'/{acct}'/2'"
return "m/48'/{coin_type}'/{acct}'/2'"
else:
if addr_type == AF_CLASSIC:
return "m/44'/0'/{acct}'"
return "m/44'/{coin_type}'/{acct}'"
elif addr_type == AF_P2WPKH_P2SH:
return "m/49'/0'/{acct}'"
return "m/49'/{coin_type}'/{acct}'"
elif addr_type == AF_P2WPKH:
return "m/84'/0'/{acct}'"
return "m/84'/{coin_type}'/{acct}'"
return None
def get_deriv_path_from_addr_type_and_acct(addr_type, acct_num, is_multisig):
chain = chains.current_chain()
# print('get_deriv_path_from_addr_type_and_acct(): addr_type={} acct={} is_multisig={}'.format(addr_type, acct_num, is_multisig))
fmt = get_deriv_fmt_from_addr_type(addr_type, is_multisig)
if fmt != None:
return fmt.format(acct=acct_num)
return fmt.format(coin_type=chain.b44_cointype,acct=acct_num)
return None
# For single sig only
def get_deriv_path_from_address_and_acct(address, acct, is_multisig):
chain = chains.current_chain()
# print('get_deriv_path_from_address_and_acct(): address={} acct={} is_multisig={}'.format(address, acct, is_multisig))
fmt = get_deriv_fmt_from_address(address, is_multisig)
if fmt != None:
return fmt.format(acct=acct)
return fmt.format(coin_type=chain.b44_cointype,acct=acct)
return None

5
ports/stm32/boards/Passport/modules/wallets/vault.py

@ -4,6 +4,7 @@
# vault.py - Export format used by some wallets
#
import chains
import stash
import ujson
from utils import xfp2str, to_str
@ -14,8 +15,10 @@ from public_constants import AF_CLASSIC, AF_P2WPKH, AF_P2WPKH_P2SH
def create_vault_export(sw_wallet=None, addr_type=None, acct_num=0, multisig=False, legacy=False):
from common import settings, system
chain = chains.current_chain()
(fw_version, _, _, _) = system.get_software_info()
acct_path = "84'/0'/{acct}'".format(acct=acct_num)
acct_path = "84'/{coin_type}'/{acct}'".format(coin_type=chain.b44_cointype,acct=acct_num)
master_xfp = xfp2str(settings.get('xfp'))
with stash.SensitiveValues() as sv:

9
ports/stm32/boards/Passport/modules/wallets/wasabi.py

@ -20,15 +20,14 @@ from common import settings, system
def create_wasabi_export(sw_wallet=None, addr_type=None, acct_num=0, multisig=False, legacy=False):
# Generate the data for a JSON file which Wasabi can open directly as a new wallet.
btc = chains.BitcoinMain
chain = chains.current_chain()
with stash.SensitiveValues() as sv:
acct_path = "m/84'/0'/{acct}'".format(acct=acct_num)
acct_path = "m/84'/{coin_type}'/{acct}'".format(coin_type=chain.b44_cointype,acct=acct_num)
node = sv.derive_path(acct_path)
xfp = xfp2str(settings.get('xfp'))
xpub = btc.serialize_public(node, AF_CLASSIC)
xpub = chain.serialize_public(node, AF_CLASSIC)
chain = chains.current_chain()
assert chain.ctype in {'BTC', 'TBTC'}, "Only Bitcoin supported"
(fw_version, _, _, _) = system.get_software_info()
@ -49,6 +48,6 @@ WasabiWallet = {
{'id':'single-sig', 'label':'Single-sig', 'addr_type': AF_P2WPKH, 'create_wallet': create_wasabi_export},
],
'export_modes': [
{'id': 'microsd', 'label': 'microSD', 'filename_pattern': '{sd}/passport-wasabi.json', 'filename_pattern_multisig': '{sd}/passport-wasabi-multisig.json'}
{'id': 'microsd', 'label': 'microSD', 'filename_pattern': '{sd}/{xfp}-wasabi.json', 'filename_pattern_multisig': '{sd}/{xfp}-wasabi-multisig.json'}
]
}

1
ports/stm32/boards/Passport/tools/cosign/Makefile

@ -20,7 +20,6 @@ CFLAGS += -I$(TOP)/include
CFLAGS += -I/usr/local/include
CFLAGS += -I$(TOP)/common/micro-ecc -DuECC_PLATFORM=uECC_x86
CFLAGS += -DPASSPORT_COSIGN_TOOL
CFLAGS += -DUSE_CRYPTO
CFLAGS += -L/usr/local/lib
LDFLAGS = -Wl,-Map,$(MAP)

81
ports/stm32/boards/Passport/tools/cosign/cosign.c

@ -11,18 +11,13 @@
#include <unistd.h>
#include <libgen.h>
#include <time.h>
#ifdef USE_CRYPTO
#include <openssl/bio.h>
#include <openssl/ec.h>
#endif /* USE_CRYPTO */
#include "fwheader.h"
#include "hash.h"
#ifdef USE_CRYPTO
#include "firmware-keys.h"
#include "uECC.h"
#endif /* USE_CRYPTO */
// This is the maximum length of "-key" + "-user", "00", "01", "02", or "03"
// Also, + 1 for the folder "/"
@ -34,11 +29,10 @@ static bool help;
static bool debug_log_level;
static bool extract_signature;
static uint8_t header[FW_HEADER_SIZE];
#ifdef USE_CRYPTO
static char *key;
extern EC_KEY *PEM_read_bio_ECPrivateKey(BIO *bp, EC_KEY **key, void *cb, void *u);
#endif /* USE_CRYPTO */
static void usage(
char *name
)
@ -47,9 +41,7 @@ static void usage(
printf("\t-d: debug logging\n"
"\t-f <firmware file>: full path to firmware file to sign\n"
"\t-h: this message\n"
#ifdef USE_CRYPTO
"\t-k <private key file>\n"
#endif /* USE_CRYPTO */
"\t-v <version>: firmware version\n"
);
exit(1);
@ -62,11 +54,7 @@ static void process_args(
{
int c = 0;
#ifdef USE_CRYPTO
while ((c = getopt(argc, argv, "dhf:v:k:x")) != -1)
#else
while ((c = getopt(argc, argv, "dhf:v:x")) != -1)
#endif /* USE_CRYPTO */
{
switch (c)
{
@ -76,11 +64,9 @@ static void process_args(
case 'v':
version = optarg;
break;
#ifdef USE_CRYPTO
case 'k':
key = optarg;
break;
#endif /* USE_CRYPTO */
case 'd':
debug_log_level = true;
break;
@ -131,7 +117,7 @@ out:
fclose(fp);
return ret;
}
#ifdef USE_CRYPTO
static uint8_t *read_private_key(
char *key
)
@ -408,12 +394,10 @@ int find_public_key(
}
return -1;
}
#endif /* USE_CRYPTO */
static void sign_firmware(
char *fw,
#ifdef USE_CRYPTO
char *key,
#endif /* USE_CRYPTO */
char *version
)
{
@ -431,18 +415,16 @@ static void sign_firmware(
uint8_t *fwptr;
uint8_t fw_hash[HASH_LEN];
uint8_t *working_signature;
#ifdef USE_CRYPTO
int rc;
uint8_t working_key = 0;
uint8_t *private_key;
uint8_t *public_key;
#endif /* USE_CRYPTO */
if (fw == NULL)
{
printf("firmware not specified\n");
return;
}
#ifdef USE_CRYPTO
if (key == NULL)
{
printf("private key not specified\n");
@ -471,7 +453,7 @@ static void sign_firmware(
}
else
working_key = rc;
#endif /* USE_CRYPTO */
tmp = strdup(fw);
filename = basename(tmp);
@ -511,16 +493,6 @@ static void sign_firmware(
return;
}
if (working_key == FW_USER_KEY)
{
sprintf(output, "%s/%s-key-user.bin", path, final_file);
}
else
{
sprintf(output, "%s/%s-key%02d.bin", path, final_file, working_key);
}
free(final_file);
if (debug_log_level)
printf("Reading %s...", fw);
fwlen = read_file(fw, &fwbuf);
@ -532,13 +504,6 @@ static void sign_firmware(
if (debug_log_level)
printf("done\n");
fp = fopen(output, "wb");
if (fp == NULL)
{
printf("failed to open %s\n", output);
goto out;
}
/*
* Test for an existing header in the firwmare. If one exists that
* means that it has been signed at least once already.
@ -562,7 +527,6 @@ static void sign_firmware(
printf("Existing header found but FW length invalid\n");
goto out;
}
#ifdef USE_CRYPTO
else if (hdrptr->signature.pubkey1 == FW_USER_KEY)
{
printf("This firmware was already signed by a user private key.\n");
@ -580,9 +544,11 @@ static void sign_firmware(
}
hdrptr->signature.pubkey2 = working_key;
#endif /* USE_CRYPTO */
working_signature = hdrptr->signature.signature2;
fwptr = fwbuf + FW_HEADER_SIZE;
// Generate output filename
sprintf(output, "%s/passport-fw-%s.bin", path, hdrptr->info.fwversion);
}
else
{
@ -598,6 +564,16 @@ static void sign_firmware(
goto out;
}
// Generate output filename
if (working_key == FW_USER_KEY)
{
sprintf(output, "%s/%s-key-user.bin", path, final_file);
}
else
{
sprintf(output, "%s/%s-key%02d.bin", path, final_file, working_key);
}
hdrptr = (passport_firmware_header_t *)header;
/* Create a new header...this is the first signature. */
@ -612,13 +588,19 @@ static void sign_firmware(
strcpy((char *)hdrptr->info.fwversion, version);
hdrptr->info.fwlength = fwlen;
#ifdef USE_CRYPTO
hdrptr->signature.pubkey1 = working_key;
#endif /* USE_CRYPTO */
working_signature = hdrptr->signature.signature1;
fwptr = fwbuf;
}
free(final_file);
fp = fopen(output, "wb");
if (fp == NULL)
{
printf("failed to open %s\n", output);
goto out;
}
if (debug_log_level)
{
printf("FW header content:\n");
@ -638,7 +620,6 @@ static void sign_firmware(
printf("\n");
}
#ifdef USE_CRYPTO
/* Encrypt the hash here... */
rc = uECC_sign(private_key,
fw_hash, sizeof(fw_hash),
@ -660,10 +641,7 @@ static void sign_firmware(
goto out;
}
}
#else
memset(working_signature, 0, SIGNATURE_LEN);
memcpy(working_signature, fw_hash, HASH_LEN);
#endif /* USE_CRYPTO */
if (debug_log_level)
{
printf("signature: ");
@ -701,6 +679,7 @@ out:
free(fwbuf);
free(output);
free(tmp);
if (fp != NULL)
fclose(fp);
}
@ -779,11 +758,7 @@ int main(int argc, char *argv[])
if (extract_signature)
dump_firmware_signature(firmware);
else
#ifdef USE_CRYPTO
sign_firmware(firmware, key, version);
#else
sign_firmware(firmware, version);
#endif /* USE_CRYPTO */
exit(0);
}

7
ports/stm32/boards/Passport/tools/version_info/version_info

@ -1,3 +1,6 @@
# SPDX-FileCopyrightText: 2021 Foundation Devices, Inc. <hello@foundationdevices.com>
# SPDX-License-Identifier: GPL-3.0-or-later
usage()
{
echo "Usage: `basename $0` <version information file name> <release string> [-h]"
@ -11,8 +14,8 @@ release=$2
[ -z "$file" ] && usage
[ -z "$release" ] && usage
echo "// SPDX-FileCopyrightText: $(date +"%Y") Foundation Devices, Inc. <hello@foundationdevices.com>" > $file
echo "// SPDX-License-Identifier: GPL-3.0-or-later" >> $file
echo "// SPDX\x2dFileCopyrightText: $(date +"%Y") Foundation Devices, Inc. <hello@foundationdevices.com>\n" > $file
echo "// SPDX\x2dLicense-Identifier: GPL-3.0-or-later\n" >> $file
echo "//" >> $file
echo "" >> $file
echo "char *build_date = \"$(date +"%b. %d, %Y")\";" >> $file

5
ports/stm32/boards/Passport/tools/word_list_gen/word_list_gen.c

@ -87,8 +87,9 @@ int compare_word_info(const void * a, const void * b) {
}
void make_num_pairs_array(const char** words, char* prefix) {
printf("// SPDX-FileCopyrightText: 2021 Foundation Devices, Inc. <hello@foundationdevices.com>\n");
printf("// SPDX-License-Identifier: GPL-3.0-or-later\n");
// Insert the hyphen all weird like this so that `reuse lint` doesn't complain about parsing this
printf("// SPDX%cFileCopyrightText: 2021 Foundation Devices, Inc. <hello@foundationdevices.com>\n", '-');
printf("// SPDX%cLicense-Identifier: GPL-3.0-or-later\n", '-');
printf("//\n\n");
printf("#include <stdint.h>\n\n");

2
py/dynruntime.mk

@ -7,7 +7,7 @@ ECHO = @echo
RM = /bin/rm
MKDIR = /bin/mkdir
PYTHON = python3
MPY_CROSS = $(MPY_DIR)/mpy-cross/mpy-cross
MPY_CROSS ?= $(MPY_DIR)/mpy-cross/mpy-cross
MPY_TOOL = $(PYTHON) $(MPY_DIR)/tools/mpy-tool.py
MPY_LD = $(PYTHON) $(MPY_DIR)/tools/mpy_ld.py

2
py/mkenv.mk

@ -63,7 +63,7 @@ endif
MAKE_MANIFEST = $(PYTHON) $(TOP)/tools/makemanifest.py
MAKE_FROZEN = $(PYTHON) $(TOP)/tools/make-frozen.py
MPY_CROSS = $(TOP)/mpy-cross/mpy-cross
MPY_CROSS ?= $(TOP)/mpy-cross/mpy-cross
MPY_TOOL = $(PYTHON) $(TOP)/tools/mpy-tool.py
MPY_LIB_DIR = $(TOP)/../micropython-lib

2
py/mkrules.mk

@ -100,7 +100,7 @@ $(HEADER_BUILD):
ifneq ($(FROZEN_MANIFEST),)
# to build frozen_content.c from a manifest
$(BUILD)/frozen_content.c: FORCE $(BUILD)/genhdr/qstrdefs.generated.h
$(Q)$(MAKE_MANIFEST) -o $@ -v "MPY_DIR=$(TOP)" -v "MPY_LIB_DIR=$(MPY_LIB_DIR)" -v "PORT_DIR=$(shell pwd)" -v "BOARD_DIR=$(BOARD_DIR)" -b "$(BUILD)" $(if $(MPY_CROSS_FLAGS),-f"$(MPY_CROSS_FLAGS)",) $(FROZEN_MANIFEST)
$(Q)$(MAKE_MANIFEST) -o $@ -v "MPY_DIR=$(TOP)" -v "MPY_LIB_DIR=$(MPY_LIB_DIR)" -v "MPY_CROSS=$(MPY_CROSS)" -v "PORT_DIR=$(shell pwd)" -v "BOARD_DIR=$(BOARD_DIR)" -b "$(BUILD)" $(if $(MPY_CROSS_FLAGS),-f"$(MPY_CROSS_FLAGS)",) $(FROZEN_MANIFEST)
ifneq ($(FROZEN_DIR),)
$(error FROZEN_DIR cannot be used in conjunction with FROZEN_MANIFEST)

2
requirements.txt

@ -0,0 +1,2 @@
autopep8==1.5.7
pycodestyle==2.7.0

2
tools/makemanifest.py

@ -216,7 +216,7 @@ def main():
# Get paths to tools
MAKE_FROZEN = VARS['MPY_DIR'] + '/tools/make-frozen.py'
MPY_CROSS = VARS['MPY_DIR'] + '/mpy-cross/mpy-cross'
MPY_CROSS = VARS['MPY_CROSS']
MPY_TOOL = VARS['MPY_DIR'] + '/tools/mpy-tool.py'
# Ensure mpy-cross is built

Loading…
Cancel
Save