Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
175 changes: 23 additions & 152 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -1,173 +1,44 @@
name: Build
name: Build SeedSigner with Keystone

on:
pull_request:
# Build on changes to this workflow files in PRs to test proposed changes
paths:
- '.github/workflows/build.yml'
push:
branches:
- main
- dev
- keystone-feature
workflow_dispatch:
inputs:
os-ref:
description: The seedsigner-os ref (tag/branch/sha1) to use
default: main
required: true

# Increment this number as part of a PR to trigger an image build for the PR
# trigger = 0

jobs:
build:
name: build
runs-on: ubuntu-latest
# Prevent resource consuming cron triggered runs in forks
if: (!github.event.repository.fork || github.event_name == 'workflow_dispatch')
strategy:
fail-fast: false
matrix:
target: [ "pi0" ] # , "pi2", "pi02w", "pi4" ] Just build Pi0 to keep within 500mb storage limit for Github free
steps:
- name: checkout seedsigner-os
uses: actions/checkout@v4
with:
repository: "3rditeration/seedsigner-os"
# use the os-ref input parameter in case of workflow_dispatch or default to main in case of cron triggers
ref: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.os-ref || 'main' }}
submodules: true
path: "seedsigner-os"
# get full history + tags for "git describe"
fetch-depth: 0

- name: checkout source
uses: actions/checkout@v4
with:
# ref defaults to repo default-branch=dev (cron) or SHA of event (workflow_dispatch)
path: "seedsigner-os/opt/rootfs-overlay/opt"
# get full history + tags for "git describe"
fetch-depth: 0

- name: Get and set meta data
run: |
# The builder_hash (seedsigner-os hash) for the cache action step key
echo "builder_hash=$(git -C seedsigner-os rev-parse --short HEAD)"| tee -a $GITHUB_ENV

# Derive tag based versions, like 0.7.0-40-g0424967 (=$tag-$number-of-commits-since-tag-$short-sha1),
# or just e.g. 0.7.0, if we are exactly on a 0.7.0 tagged commit.
# --always to fall back to commit sha, if no tag present like in partial forks of the repo
os_version="$(git -C seedsigner-os describe --tags --always)"
source_version="$(git -C seedsigner-os/opt/rootfs-overlay/opt describe --tags --always)"

# Combine seedsigner and seedsigner-os version into one version string and squash the versions, if
# they are identical: So os_version=0.7.0 + source_version=0.7.0 combine to just only "0.7.0",
# whereas os_version=0.6.0-61-g9fafebe + source_version=0.7.0-40-g0424967 combine to "os0.6.0-61-g9fafebe_sw0.7.0-40-g0424967"
if [ "${os_version}" = "${source_version}" ]; then
# seedsigner + seedsigner_os have the same tag
echo "img_version=${source_version}"| tee -a $GITHUB_ENV
else
echo "img_version=os${os_version}_sw${source_version}"| tee -a $GITHUB_ENV
fi

- name: delete unnecessary files
run: |
cd seedsigner-os/opt/rootfs-overlay/opt
find . -mindepth 1 -maxdepth 1 ! -name src -exec rm -rf {} +
ls -la .
ls -la src

- name: restore build cache
uses: actions/cache@v4
# Caching reduces the build time to ~50% (currently: ~30 mins instead of ~1 hour,
# while consuming ~850 MB storage space).
with:
path: |
~/.buildroot-ccache/
seedsigner-os/buildroot_dl
key: build-cache-${{ matrix.target }}-${{ env.builder_hash }}
restore-keys: |
build-cache-${{ matrix.target }}-

- name: Create build container
run: |
cd seedsigner-os
docker build -t seedsigner-os-build .

- name: build
run: |
mkdir -p \
~/.buildroot-ccache \
seedsigner-os/buildroot_dl
docker run \
--rm \
-v "$(pwd)/seedsigner-os/opt:/opt" \
-v "$(pwd)/seedsigner-os/images:/images" \
-v "$(pwd)/seedsigner-os/buildroot_dl:/buildroot_dl" \
-v "${HOME}/.buildroot-ccache:/root/.buildroot-ccache" \
seedsigner-os-build \
--${{ matrix.target }} --skip-repo --no-clean --smartcard
sudo chown -R $USER:$USER seedsigner-os/images seedsigner-os/buildroot_dl ~/.buildroot-ccache/

- name: list image (before rename)
run: |
ls -la seedsigner-os/images

- name: rename image
run: |
cd seedsigner-os/images
mv seedsigner_os*.img.zip seedsigner_os.${{ env.img_version }}.${{ matrix.target }}.img.zip

- name: print sha256sum
run: |
cd seedsigner-os/images
sha256sum *.img.zip

- name: list image (after rename)
run: |
ls -la seedsigner-os/images

- name: upload images
uses: actions/upload-artifact@v4
with:
name: seedsigner_os_images-${{ matrix.target }}
path: "seedsigner-os/images/*.img.zip"
if-no-files-found: error
# maximum 90 days retention
retention-days: 90

sha256sum:
name: calculate sha256sum
runs-on: ubuntu-latest
needs: build
steps:
- name: download images
uses: actions/download-artifact@v4
- name: Checkout repository
uses: actions/checkout@v3

- name: Set up Python
uses: actions/setup-python@v4
with:
path: images
python-version: '3.9'

- name: list images
- name: Install build dependencies
run: |
ls -lRa images
sudo apt-get update
sudo apt-get install -y build-essential \
python3-pil libjpeg-dev libusb-1.0-0-dev libudev-dev \
cmake device-tree-compiler qemu-user-static binfmt-support

- name: get seedsigner latest commit hash
id: get-seedsigner-hash
run: |
git init
echo "source_hash=$(git rev-parse --short ${{ github.sha }})" >> $GITHUB_ENV
- name: Set up Docker
uses: docker/setup-buildx-action@v3

- name: write sha256sum
- name: Build SeedSigner Image using Docker
run: |
cd images
# each downloaded image is in its own subfolder
find . -name "*.img.zip" -exec mv {} . \;
sha256sum *.img.zip > seedsigner_os.${{ env.source_hash }}.sha256
cd seedsigner
docker build -t seedsigner-build .
docker run --rm -v ${{ github.workspace }}:/mnt seedsigner-build \
cp /work/seedsigner.img /mnt/seedsigner.img

- name: upload checksums
- name: Upload image artifact
uses: actions/upload-artifact@v4
with:
name: seedsigner_os_images_sha256
path: "images/*.sha256"
if-no-files-found: error
# maximum 90 days retention
retention-days: 90
name: seedsigner-image
path: seedsigner.img
Empty file added cd
Empty file.
21 changes: 21 additions & 0 deletions helpers/keystone.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# keystone.py
from seedsigner.gui.components.qr_type import QRType
from seedsigner.gui.components.qr_encoder import QREncoder
from seedsigner.gui.screens.qr_display_screen import QRDisplayScreen

def show_keystone_export(gui, seed_phrase):
export_data = {
"type": "keystone-eip4527-export",
"mnemonic": seed_phrase,
}

import json
payload = json.dumps(export_data)

qr_encoder = QREncoder(gui, QRType.TEXT, payload)
screen = QRDisplayScreen(gui, qr_encoder)
screen.display_qr()

def sign_keystone_eth_tx(gui):
from seedsigner.gui.screens import info_screen
info_screen.display_message(gui, "Sign ETH TX", "Feature coming soon.")
35 changes: 35 additions & 0 deletions seedsigner/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
FROM ubuntu:24.04

# 1. Install build tools
RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y \
git git-lfs build-essential python3 python3-pip \
dosfstools e2fsprogs qemu-user-static unzip wget

WORKDIR /seedsigner

# 2. Copy all source + patches
# COPY . .

COPY patches/ patches/

RUN for patch in patches/*.patch; do \
echo "Applying $patch..."; \
patch -p1 < "$patch"; \
done

# 3. Pull LFS files
# RUN git lfs install && git lfs pull

# 4. Apply your patches
RUN for patch in patches/*.patch; do \
echo "Applying $patch..."; \
patch -p1 < "$patch"; \
done

# 5. Install Python deps
RUN pip3 install --upgrade pip \
&& pip3 install -r requirements.txt ur-registry-eth

# 6. Build the SD-card image
RUN chmod +x scripts/create_img.sh \
&& ./scripts/create_img.sh
7 changes: 7 additions & 0 deletions seedsigner/controllers/main_menu_controller.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from seedsigner.helpers import keystone

...
main_menu.add_item(MenuItem(
label="Export as Keystone (EIP-4527)",
on_select=lambda: keystone.show_keystone_export(self.gui, self.seed_phrase)
))
7 changes: 7 additions & 0 deletions seedsigner/controllers/signers_menu_controller.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from seedsigner.helpers import keystone

...
signer_menu.add_item(MenuItem(
label="Sign Keystone ETH-TX (EIP-4527)",
on_select=keystone.sign_keystone_eth_tx
))
21 changes: 21 additions & 0 deletions seedsigner/helpers/keystone.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# keystone.py
from seedsigner.gui.components.qr_type import QRType
from seedsigner.gui.components.qr_encoder import QREncoder
from seedsigner.gui.screens.qr_display_screen import QRDisplayScreen

def show_keystone_export(gui, seed_phrase):
export_data = {
"type": "keystone-eip4527-export",
"mnemonic": seed_phrase,
}

import json
payload = json.dumps(export_data)

qr_encoder = QREncoder(gui, QRType.TEXT, payload)
screen = QRDisplayScreen(gui, qr_encoder)
screen.display_qr()

def sign_keystone_eth_tx(gui):
from seedsigner.gui.screens import info_screen
info_screen.display_message(gui, "Sign ETH TX", "Feature coming soon.")
45 changes: 45 additions & 0 deletions seedsigner/patches/0001-add-keystone-wallet-pairing.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
From a1b2c3d4e5f6g7 Mon Sep 17 00:00:00 2001
From: Your Name <you@example.com>
Date: 2025-08-07
Subject: [PATCH 1/2] Add Keystone pairing QR export

---
seedsigner/gui/menus/main_menu.py | 12 ++++++++++++
seedsigner/helpers/keystone.py | 19 +++++++++++++++++++
2 files changed, 31 insertions(+)
create mode 100644 seedsigner/helpers/keystone.py

diff --git a/seedsigner/gui/menus/main_menu.py b/seedsigner/gui/menus/main_menu.py
index 1234567..89abcde 100644
--- a/seedsigner/gui/menus/main_menu.py
+++ b/seedsigner/gui/menus/main_menu.py
@@ def MAIN_MENU(self):
+ MenuOption("Export Keystone Pairing QR", "export_keystone_pairing_qr"),

@@ def _handle_selected_option(self, option):
+ elif option == "Export Keystone Pairing QR":
+ from seedsigner.helpers import keystone
+ qr_data = keystone.get_pairing_qr()
+ self.qr_view_display_screen(
+ qr_data,
+ title="Keystone Pairing QR"
+ )

diff --git a/seedsigner/helpers/keystone.py b/seedsigner/helpers/keystone.py
new file mode 100644
index 0000000..1111111
--- /dev/null
+++ b/seedsigner/helpers/keystone.py
@@
+def get_pairing_qr():
+ # Replace with your actual fingerprint and metadata
+ device = {
+ "type": "keystone",
+ "version": "1.0.0",
+ "model": "SeedSigner",
+ "fingerprint": "12345678",
+ "xfp": "12345678"
+ }
+ import json
+ return json.dumps(device)

43 changes: 43 additions & 0 deletions seedsigner/patches/0002-add-keystone-eth-sign.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
From b7c8d9e0f1a2b3 Mon Sep 17 00:00:00 2001
From: Your Name <you@example.com>
Date: 2025-08-07
Subject: [PATCH 2/2] Add Keystone ETH signing support

---
seedsigner/gui/menus/signers_menu.py | 12 ++++++++++++
seedsigner/helpers/keystone_sign.py | 30 ++++++++++++++++++++++++++++++
2 files changed, 42 insertions(+)
create mode 100644 seedsigner/helpers/keystone_sign.py

diff --git a/seedsigner/gui/menus/signers_menu.py b/seedsigner/gui/menus/signers_menu.py
index abcdef0..1234567 100644
--- a/seedsigner/gui/menus/signers_menu.py
+++ b/seedsigner/gui/menus/signers_menu.py
@@ def SIGNER_MENU(self):
+ MenuOption("Sign Keystone ETH-TX", "sign_keystone_eth_tx"),

@@ def _handle_selected_option(self, option):
+ elif option == "Sign Keystone ETH-TX":
+ from seedsigner.helpers import keystone_sign
+ eth_tx = self.qr_capture_input_screen("Scan ETH TX QR")
+ signed = keystone_sign.sign_eth_tx(eth_tx)
+ self.qr_view_display_screen(
+ signed,
+ title="Signed ETH TX"
+ )

diff --git a/seedsigner/helpers/keystone_sign.py b/seedsigner/helpers/keystone_sign.py
new file mode 100644
index 0000000..2222222
--- /dev/null
+++ b/seedsigner/helpers/keystone_sign.py
@@
+def sign_eth_tx(payload):
+ import json
+ # Mock signing process
+ try:
+ data = json.loads(payload)
+ data["signature"] = "0xdeadbeef" # Replace with actual ECDSA logic
+ return json.dumps(data)
+ except Exception as e:
+ return json.dumps({"error": str(e)})