Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
accd11d
release(7.15.0-rc18): stage final audit candidate
BitHighlander Jul 24, 2026
cb68d07
security(crypto): vendor audited trezor-crypto for rc18
BitHighlander Jul 25, 2026
6b8cfbe
ci: scope vendored crypto secret-scan exceptions
BitHighlander Jul 25, 2026
5e94bc6
ci: give cppcheck headroom for vendored includes
BitHighlander Jul 25, 2026
2d1bac0
fix(security): close RC18 release audit findings
BitHighlander Jul 25, 2026
faae555
fix(ci): make RC18 invariant gate checkout-safe
BitHighlander Jul 25, 2026
727fb3b
fix(ci): allow sanitizer codepoint tests to finish
BitHighlander Jul 25, 2026
73459b2
fix(release): restore Zcash privacy to regular RC18
BitHighlander Jul 25, 2026
bfc0f3e
fix(zcash): make Pallas secret scalar path constant-time
BitHighlander Jul 25, 2026
030c43e
fix(zcash): restore practical Orchard signing latency
BitHighlander Jul 26, 2026
b6e6e5f
fix(zcash): stream signing progress and cut public work
BitHighlander Jul 26, 2026
b3dbf42
fix(zcash): skip dummy spends and unblock signing
BitHighlander Jul 26, 2026
d4a9453
fix(zcash): report Orchard key derivation progress
BitHighlander Jul 26, 2026
94ffc1a
fix(zcash): report action verification progress
BitHighlander Jul 26, 2026
37c42e5
chore(rc18): repin reviewed dependency heads
BitHighlander Jul 26, 2026
50b3f85
chore(rc18): align nested protocol pin
BitHighlander Jul 26, 2026
9d28cb3
chore(rc18): repin Python integration fix
BitHighlander Jul 26, 2026
5c5a6b9
build(crypto): restore reviewed trezor-firmware fork
BitHighlander Jul 26, 2026
a049a8b
ci(gitleaks): preserve historical crypto allowlists
BitHighlander Jul 26, 2026
5003ee6
chore(rc18): repin corrected Python audit report
BitHighlander Jul 26, 2026
a643f62
chore(rc18): repin merged crypto dependency
BitHighlander Jul 26, 2026
fd1012c
docs(rc18): drop out-of-scope Tendermint roadmap
BitHighlander Jul 26, 2026
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
266 changes: 208 additions & 58 deletions .github/workflows/ci.yml

Large diffs are not rendered by default.

186 changes: 148 additions & 38 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
# KeepKey Firmware Release Pipeline
#
# Triggered by version tags (v*) on master.
# Triggered by version tags (v*).
# Builds firmware, computes reproducible hashes,
# and creates a draft GitHub Release with all artifacts.
#
# Git-flow: tag master after merging a release/* or hotfix/* branch.
# Tag a commit only after its CI workflow has completed successfully.
#
# Usage:
# git tag v7.11.0
# git push origin v7.11.0
# git tag -a v7.15.0-rc17 -m "KeepKey firmware 7.15.0 RC17 test candidate"
# git push origin v7.15.0-rc17

name: Release

Expand All @@ -18,7 +18,7 @@ on:
- 'v*'

env:
BASE_IMAGE: kktech/firmware:v15
BASE_IMAGE: kktech/firmware@sha256:7438e53933d47d53157ed6d96d864cb208597e62dce26235ace09d1063427fa2

permissions:
contents: write
Expand All @@ -29,34 +29,83 @@ jobs:
timeout-minutes: 3
outputs:
fw_version: ${{ steps.version.outputs.fw_version }}
tag_name: ${{ steps.version.outputs.tag_name }}
is_prerelease: ${{ steps.version.outputs.is_prerelease }}
steps:
- uses: actions/checkout@v6
with:
submodules: recursive
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6

- name: Extract and verify version
id: version
run: |
TAG_VERSION="${GITHUB_REF_NAME#v}"
FW_VERSION=$(sed -n '/^project/,/)/p' CMakeLists.txt | grep -oP '\d+\.\d+\.\d+')
echo "fw_version=${FW_VERSION}" >> "$GITHUB_OUTPUT"
IS_PRERELEASE=false
RC_PREFIX="${FW_VERSION}-rc"

if [ "$TAG_VERSION" != "$FW_VERSION" ]; then
echo "::error::Tag (${TAG_VERSION}) != CMakeLists.txt (${FW_VERSION})"
if [[ "$TAG_VERSION" != "${RC_PREFIX}"* ]]; then
echo "::error::Tag (${TAG_VERSION}) must be ${FW_VERSION} or ${RC_PREFIX}<number>"
exit 1
fi

RC_NUMBER="${TAG_VERSION#"${RC_PREFIX}"}"
if ! [[ "$RC_NUMBER" =~ ^[0-9]+$ ]]; then
echo "::error::Invalid release-candidate tag (${TAG_VERSION}); expected ${RC_PREFIX}<number>"
exit 1
fi
IS_PRERELEASE=true
fi

{
echo "fw_version=${FW_VERSION}"
echo "tag_name=${GITHUB_REF_NAME}"
echo "is_prerelease=${IS_PRERELEASE}"
} >> "$GITHUB_OUTPUT"

- name: Require green CI on the tagged commit
env:
GH_TOKEN: ${{ github.token }}
run: |
# A tag on a red (or untested) commit must not produce release
# artifacts. The tagged SHA already ran the CI workflow on its
# branch push; require that run to exist and have succeeded.
CONCLUSION=$(gh run list --repo "$GITHUB_REPOSITORY" \
--workflow CI --commit "$GITHUB_SHA" \
--json status,conclusion \
--jq '[.[] | select(.status == "completed")] | map(.conclusion) | first')
echo "CI conclusion for $GITHUB_SHA: ${CONCLUSION:-none}"
if [ "$CONCLUSION" != "success" ]; then
echo "::error::No successful CI run found for ${GITHUB_SHA} — refusing to release."
exit 1
fi

build-firmware:
needs: validate
runs-on: ubuntu-latest
timeout-minutes: 20
strategy:
fail-fast: false
matrix:
include:
- variant: full
cmake_flags: ""
- variant: bitcoin-only
cmake_flags: "-DKK_BITCOIN_ONLY=ON"
steps:
- uses: actions/checkout@v6
with:
submodules: recursive
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6

- name: Init required submodules
run: |
git submodule update --init deps/crypto/trezor-firmware
git submodule update --init deps/device-protocol
git submodule update --init --recursive deps/python-keepkey
git submodule update --init deps/googletest
git submodule update --init deps/qrenc/QR-Code-generator
git submodule update --init deps/sca-hardening/SecAESSTM32

- name: Cache base image
id: cache-base
uses: actions/cache@v5
uses: actions/cache@caa296126883cff596d87d8935842f9db880ef25 # v5
with:
path: /tmp/base-image.tar
key: base-image-${{ env.BASE_IMAGE }}
Expand All @@ -71,26 +120,39 @@ jobs:
if: steps.cache-base.outputs.cache-hit == 'true'
run: docker load -i /tmp/base-image.tar

- name: Cross-compile firmware
- name: Cross-compile firmware (${{ matrix.variant }})
run: |
docker run --rm \
-v ${{ github.workspace }}:/root/keepkey-firmware:z \
${{ env.BASE_IMAGE }} /bin/sh -c "\
mkdir /root/build && cd /root/build && \
cmake -C /root/keepkey-firmware/cmake/caches/device.cmake /root/keepkey-firmware \
-DCMAKE_BUILD_TYPE=MinSizeRel \
-DCMAKE_COLOR_MAKEFILE=ON && \
-DCMAKE_COLOR_MAKEFILE=ON \
${{ matrix.cmake_flags }} && \
make && \
mkdir -p /root/keepkey-firmware/release && \
cp bin/firmware.keepkey.bin /root/keepkey-firmware/release/ && \
cp bin/firmware.keepkey.elf /root/keepkey-firmware/release/ && \
cp bin/bootloader.bin /root/keepkey-firmware/release/ 2>/dev/null || true && \
find . -name '*.su' -print0 | tar czf /root/keepkey-firmware/release/stack-usage.tgz --null -T - && \
chmod -R a+rw /root/keepkey-firmware/release"

# Same SRAM budget gate CI enforces (rc8 boot-fault class): release
# artifacts must clear it too, not just the 16 KiB linker ASSERT.
- name: SRAM budget gate (${{ matrix.variant }})
run: |
pip install --quiet pyelftools
python3 tools/check_sram_budget.py \
--elf release/firmware.keepkey.elf \
--su-tar release/stack-usage.tgz \
--budgets tools/sram-budgets.json \
--variant "${{ matrix.variant }}"

- name: Compute hashes
working-directory: release
run: |
echo "# KeepKey Firmware v${{ needs.validate.outputs.fw_version }} — Hash Manifest" > HASHES.txt
echo "# KeepKey Firmware v${{ needs.validate.outputs.fw_version }} (${{ matrix.variant }}) — Hash Manifest" > HASHES.txt
echo "" >> HASHES.txt
for f in *.bin; do
[ -f "$f" ] || continue
Expand All @@ -109,30 +171,63 @@ jobs:
working-directory: release
run: |
VER="${{ needs.validate.outputs.fw_version }}"
[ -f firmware.keepkey.bin ] && mv firmware.keepkey.bin "firmware.keepkey.v${VER}.bin"
[ -f firmware.keepkey.elf ] && mv firmware.keepkey.elf "firmware.keepkey.v${VER}.elf"
[ -f bootloader.bin ] && mv bootloader.bin "bootloader.v${VER}.bin"
VARIANT="${{ matrix.variant }}"
[ -f firmware.keepkey.bin ] && mv firmware.keepkey.bin "firmware.keepkey.v${VER}-${VARIANT}.bin"
[ -f firmware.keepkey.elf ] && mv firmware.keepkey.elf "firmware.keepkey.v${VER}-${VARIANT}.elf"
[ -f bootloader.bin ] && mv bootloader.bin "bootloader.v${VER}-${VARIANT}.bin"
mv HASHES.txt "HASHES-${VARIANT}.txt"
ls -lh

- name: Record build provenance
working-directory: release
run: |
VARIANT="${{ matrix.variant }}"
{
echo "firmware_commit=${GITHUB_SHA}"
echo "source_repository=${GITHUB_REPOSITORY}"
echo "source_ref=${GITHUB_REF}"
echo "workflow_ref=${GITHUB_WORKFLOW_REF}"
echo "builder_image=${BASE_IMAGE}"
echo "runner_image=${ImageOS:-unknown}"
echo "variant=${VARIANT}"
echo "cmake_flags=${{ matrix.cmake_flags }}"
} > "PROVENANCE-${VARIANT}.txt"
cat "PROVENANCE-${VARIANT}.txt"

- name: Upload release artifacts
uses: actions/upload-artifact@v7
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: release-firmware
name: release-firmware-${{ matrix.variant }}
path: release/*
retention-days: 90

test:
needs: validate
runs-on: ubuntu-latest
timeout-minutes: 20
strategy:
fail-fast: false
matrix:
include:
- variant: full
cmake_flags: ""
- variant: bitcoin-only
cmake_flags: "-DKK_BITCOIN_ONLY=ON"
steps:
- uses: actions/checkout@v6
with:
submodules: recursive
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6

- name: Init required submodules
run: |
git submodule update --init deps/crypto/trezor-firmware
git submodule update --init deps/device-protocol
git submodule update --init --recursive deps/python-keepkey
git submodule update --init deps/googletest
git submodule update --init deps/qrenc/QR-Code-generator
git submodule update --init deps/sca-hardening/SecAESSTM32

- name: Cache base image
id: cache-base
uses: actions/cache@v5
uses: actions/cache@caa296126883cff596d87d8935842f9db880ef25 # v5
with:
path: /tmp/base-image.tar
key: base-image-${{ env.BASE_IMAGE }}
Expand All @@ -147,46 +242,60 @@ jobs:
if: steps.cache-base.outputs.cache-hit == 'true'
run: docker load -i /tmp/base-image.tar

- name: Build and test emulator
- name: Build and test emulator (${{ matrix.variant }})
run: |
docker build -t kkemu-release -f scripts/emulator/Dockerfile .
docker run --rm --entrypoint /bin/sh kkemu-release \
docker build -t kkemu-release-${{ matrix.variant }} \
--build-arg coinsupport="${{ matrix.cmake_flags }}" \
-f scripts/emulator/Dockerfile .
docker run --rm --entrypoint /bin/sh kkemu-release-${{ matrix.variant }} \
-c "make xunit; RC=\$?; exit \$RC"

create-release:
needs: [validate, build-firmware, test]
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6

- name: Download firmware artifacts
uses: actions/download-artifact@v8
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
name: release-firmware
pattern: release-firmware-*
path: artifacts
merge-multiple: true

- name: Prepare release assets
run: |
mkdir -p release-assets
cp artifacts/*.bin artifacts/*.elf artifacts/HASHES.txt release-assets/
cp artifacts/*.bin artifacts/*.elf artifacts/HASHES-*.txt \
artifacts/PROVENANCE-*.txt release-assets/
ls -lh release-assets/

- name: Generate release body
run: |
VER="${{ needs.validate.outputs.fw_version }}"
cat > release-body.md <<EOF
## KeepKey Firmware v${VER}
## KeepKey Firmware ${{ needs.validate.outputs.tag_name }}

### Variants
- **full (regular)** — \`firmware.keepkey.v${VER}-full.bin\` — all supported chains, including Zcash shielded/Orchard
- **bitcoin-only** — \`firmware.keepkey.v${VER}-bitcoin-only.bin\` — BTC-only, non-BTC coins stripped

### Reproducible Build Verification
\`\`\`bash
./scripts/build/docker/device/release.sh
tail -c +257 bin/firmware.keepkey.bin | shasum -a 256
# bitcoin-only:
# ./scripts/build/docker/device/release.sh -DKK_BITCOIN_ONLY=ON
# (extra args pass through to cmake) and compare against the
# matching HASHES-<variant>.txt
\`\`\`

> **DRAFT** — firmware must be signed by 3/5 key holders before publishing.
> **DRAFT TEST CANDIDATE** — RC artifacts are unsigned and intended for
> release-candidate testing. Firmware must be signed by 3/5 key holders
> before publishing a production release.

### Signing Checklist
### Signing Checklist (per variant)
- [ ] Built on multiple machines, hashes match
- [ ] Signed on air-gapped machine (3/5 signers)
- [ ] Storage upgrade tested on production device
Expand All @@ -195,10 +304,11 @@ jobs:
EOF

- name: Create draft release
uses: softprops/action-gh-release@v2
uses: softprops/action-gh-release@3bb12739c298aeb8a4eeaf626c5b8d85266b0e65 # v2
with:
draft: true
name: "Firmware v${{ needs.validate.outputs.fw_version }}"
prerelease: ${{ needs.validate.outputs.is_prerelease }}
name: "Firmware ${{ needs.validate.outputs.tag_name }}"
body_path: release-body.md
files: release-assets/*
fail_on_unmatched_files: true
28 changes: 28 additions & 0 deletions .gitleaks.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
title = "KeepKey firmware Gitleaks configuration"

[extend]
useDefault = true

[[allowlists]]
description = "Published AES test vectors in current and historical trezor-crypto layouts"
targetRules = ["generic-api-key"]
condition = "AND"
regexTarget = "line"
paths = [
'''^deps/crypto/trezor-firmware/crypto/aes/aestst\.c$''',
'''^deps/crypto/trezor-crypto/aes/aestst\.c$''',
]
regexes = ['''(?i)^[[:space:]]*//[[:space:]]*key[[:space:]]*=[[:space:]]*[0-9a-f]+[[:space:]]*$''']

[[allowlists]]
description = "Ed25519 C type names in current and historical trezor-crypto layouts"
targetRules = ["generic-api-key"]
condition = "AND"
regexTarget = "line"
paths = [
'''^deps/crypto/trezor-firmware/crypto/ed25519-donna/ed25519-(blake2b|keccak|sha3)\.h$''',
'''^deps/crypto/trezor-firmware/crypto/ed25519-donna/ed25519\.[ch]$''',
'''^deps/crypto/trezor-crypto/ed25519-donna/ed25519-(blake2b|keccak|sha3)\.h$''',
'''^deps/crypto/trezor-crypto/ed25519-donna/ed25519\.[ch]$''',
]
regexes = ['''ed25519.*secret_key.*signature''']
6 changes: 3 additions & 3 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[submodule "deps/device-protocol"]
path = deps/device-protocol
url = https://github.com/keepkey/device-protocol.git
branch = master
url = https://github.com/keepkey/device-protocol.git
branch = up/release-protocol
[submodule "deps/trezor-firmware"]
path = deps/crypto/trezor-firmware
url = https://github.com/keepkey/trezor-firmware.git
Expand All @@ -14,7 +14,7 @@ url = https://github.com/keepkey/code-signing-keys.git
[submodule "deps/python-keepkey"]
path = deps/python-keepkey
url = https://github.com/keepkey/python-keepkey.git
branch = master
branch = reconcile/upstream-sync
[submodule "deps/qrenc/QR-Code-generator"]
path = deps/qrenc/QR-Code-generator
url = https://github.com/keepkey/QR-Code-generator.git
Expand Down
Loading
Loading