Skip to content

release: python-keepkey 7.14.1 message-signing bindings - #194

Merged
BitHighlander merged 8 commits into
masterfrom
release/7.14.1-python-keepkey
May 9, 2026
Merged

release: python-keepkey 7.14.1 message-signing bindings#194
BitHighlander merged 8 commits into
masterfrom
release/7.14.1-python-keepkey

Conversation

@BitHighlander

@BitHighlanderBitHighlander commented Apr 30, 2026

Copy link
Copy Markdown
Contributor

Summary

Prepare python-keepkey for the firmware 7.14.1 line and align it with the non-Zcash device-protocol sync in keepkey/device-protocol#103.

This PR includes:

  • bump setup.py from 7.0.3 to 7.14.1
  • bump the device-protocol submodule to the cleaned 7.14.1 message-signing protocol commit
  • regenerate Python bindings for:
    • Solana SolanaSignOffchainMessage / SolanaOffchainMessageSignature
    • TON TonSignMessage / TonMessageSignature
    • TRON TronSignMessage, TronMessageSignature, TronVerifyMessage, TronSignTypedHash, TronTypedDataSignature
  • add thin client helpers for those new Solana/TON/TRON messages
  • add a pure Python mapping smoke test for the new wire IDs and generated classes

This intentionally does not add new Zcash protocol fields, seed-fingerprint helpers, or dylib emulator transport work.

Validation

  • PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION=python python3 -m unittest tests.test_message_signing_protocol_bindings
  • PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION=python python3 -m py_compile keepkeylib/client.py keepkeylib/messages_pb2.py keepkeylib/messages_solana_pb2.py keepkeylib/messages_ton_pb2.py keepkeylib/messages_tron_pb2.py tests/test_message_signing_protocol_bindings.py setup.py

@BitHighlanderBitHighlander changed the title release: python-keepkey 7.14.1release: python-keepkey 7.14.1 message-signing bindingsApr 30, 2026
…ntics
The existing test_dylib_confirm_flow covers the caller-driven polling
contract — Initialize / Wipe / LoadDevice / GetAddress — but never asks
the firmware for a layout. Two changes that just landed in the firmware
emulator runtime PR (BitHighlander/keepkey-firmware#217) need functional
coverage that confirm-flow doesn't provide:
1. RINGBUF_CAPACITY in lib/emulator/ringbuf.h was bumped from 32 to
128. DebugLinkState's 2048-byte `layout` plus the rest of the message
serializes to ~44 HID reports through the output ring; the previous
capacity left effective room for 31 reports, so screenshot capture
truncated mid-layout (msg_debug_write ignores emulatorSocketWrite's
0-on-full return).
2. fsm_msgDebugLinkGetState in lib/firmware/fsm_msg_debug.h now does a
single display_refresh() instead of force_animation_start() +
animate(). The old form overwrote static layouts with stale animation
frames or no-ops depending on queue state, so screenshots captured
something different from what the user was seeing.
Both fixes are functionally invisible to the existing test suite. Without
these tests, regressing either change ships green.
This commit adds:
- tests/test_dylib_screenshot.py — four tests:
* test_layout_round_trip_fits_through_ring (RINGBUF_CAPACITY)
* test_layout_repeated_reads_no_truncation (RINGBUF_CAPACITY)
* test_layout_stable_across_idle_reads (canvas semantics)
* test_layout_features_dont_corrupt_capture (iface separation)
Constructs a fresh KeepKeyDebuglinkClient against the dylib singleton
WITHOUT going through common.KeepKeyTest.setUp — that fixture wipes the
device on every test and exercises the confirm-flow path that
test_dylib_confirm_flow is itself a pending regression for. Reading a
layout doesn't require any of that; we just init and ask DebugLink for
the home-screen capture.
- tests/config.py — explicit-transport precedence fix:
Previously HID/WebUSB were always autodetected first. With a real
KeepKey plugged in, KK_TRANSPORT=dylib was silently overridden — the
dylib regression suite would either route to hardware or crash on
hid.pyx. Now the explicit env var (KK_TRANSPORT=dylib) skips hardware
enumeration entirely, the dylib path runs as requested, and the default
(no env var set) falls back to the existing UDP behavior.
Verified locally:
cmake -DKK_EMULATOR=1 -DKK_BUILD_DYLIB=1 -DKK_DEBUG_LINK=ON \
-DCMAKE_POLICY_VERSION_MINIMUM=3.5 -B build-emu .
cmake --build build-emu --target kkemulator_dylib
KK_TRANSPORT=dylib KK_DYLIB=build-emu/lib/libkkemu.dylib \
PYTHONPATH=keepkeylib:. python -m pytest tests/test_dylib_screenshot.py
======================== 4 passed in 0.36s ========================
Out of scope: SignTx + other multi-step flows that go through
confirm_helper. They share the same hang as test_dylib_confirm_flow's
test_load_device_with_auto_confirm — copying the pattern would just
produce a second red regression for the same underlying firmware bug,
not new coverage. Once the confirm-flow regression goes green, signtx
expansion is a follow-up.
Same firmware as the standalone UDP kkemu binary, loaded in-process via
ctypes. Lets python-keepkey exercise the firmware contract that the
keepkey-vault FFI path imposes — most importantly, the caller-driven
polling model (no daemon thread to call kkemu_poll for you).
- keepkeylib/transport_dylib.py: DylibState (process-wide singleton over
ctypes-loaded libkkemu) + DylibTransport (one per iface 0/1).
Pumps kkemu_poll on every read/write so the firmware actually makes
forward progress on caller turns.
- tests/config.py: KK_TRANSPORT=dylib KK_DYLIB=/path/to/libkkemu.dylib
routes the same fixture to the FFI transport instead of UDP.
- tests/test_dylib_confirm_flow.py: regression for the confirm-flow
contract (Initialize, WipeDevice, LoadDevice, GetAddress). Skipped
unless KK_TRANSPORT=dylib so it won't break the default UDP run.
Reproduces the keepkey-vault hang deterministically: Initialize round-
trips fine, wipe_device hangs because confirm_helper busy-loops on a
ButtonAck the dylib silently consumed but never delivered. Caught in
~10s, no electrobun / bun stack required.
Run:
cd tests && KK_TRANSPORT=dylib KK_DYLIB=.../libkkemu.dylib \
PYTHONPATH=..:../keepkeylib python3 -m pytest \
test_dylib_confirm_flow.py -v
…ANSPORT, split confirm-flow setUp
Three findings from review of PR #14:
#1 (High) test_dylib_confirm_flow used common.KeepKeyTest.setUp which calls
wipe_device() — the same path the file's pending regression is for.
Hangs in setUp can't be classified by xfail or interrupted by
pytest-timeout, so test_features_round_trip ("just Initialize") was
actually wipe + Initialize. Refactored to construct
KeepKeyDebuglinkClient directly in setUp (matching test_dylib_screenshot's
pattern), moved wipe + load_device into the one pending test.
Tried the reviewer-suggested @pytest.mark.xfail(strict=True) +
@pytest.mark.timeout combo. pytest-timeout (both signal and thread
methods) cannot interrupt the C-level kkemu_poll busy-loop — the
hang locks up the entire test runner instead of failing the test.
Switched to @unittest.skip with explicit rationale documenting
exactly that, plus the promotion path: when firmware lands the
confirm fix, drop the skip; if a future change makes kkemu_poll
GIL-friendly, switch back to xfail+timeout.
#2 (Medium) tests/config.py treated any non-empty KK_TRANSPORT as
"explicit" and skipped HID/WebUSB autodetect, but only "dylib" was
actually handled. A typo like KK_TRANSPORT=dyllib silently fell
through to UDP with hardware disabled. Now scoped to a
_KNOWN_TRANSPORTS set; unsupported values raise at config import,
surfacing typos at test collection time. Verified end-to-end:
`KK_TRANSPORT=dyllib pytest test_msg_signtx.py` now errors on
collection with the typo'd value in the message.
#3 (Medium/Low) DylibTransport.ready_to_read appended raw frame bytes
to read_buffer but DylibTransport._pump_one stripped the leading '?'
HID marker first. Inconsistent stripping corrupts multi-frame message
reassembly: _read_headers can scan a stray '?' from one chunk into
the middle of contiguous payload bytes from another, decoding the
wrong message-type / length.
Centralised the read+strip into a private _poll_and_stash helper
shared by both ready_to_read (no sleep) and _pump_one (sleeps on
miss). Now the buffer always contains continuation+payload bytes
only; the leading '?' is stripped at the single point of stashing.
Trailing HID padding zeros from short messages are still tolerated
by _read_headers' magic-character search.
Verified locally:
KK_TRANSPORT=dylib KK_DYLIB=build-emu/lib/libkkemu.dylib \
pytest tests/test_dylib_screenshot.py tests/test_dylib_confirm_flow.py
================== 5 passed, 1 skipped in 0.15s ==================

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Updates python-keepkey for the 7.14.1 firmware/device-protocol line by regenerating protocol bindings for new Solana/TON/TRON message-signing messages, adding thin client helpers for them, and introducing dylib-based regression tests for emulator transport behavior.

Changes:

  • Bumps the package release to 7.14.1 and updates shared message type bindings for new Solana, TON, and TRON signing messages.
  • Adds client convenience methods for Solana off-chain signing, TRON message/typed-hash signing and verification, and TON message signing.
  • Introduces new regression tests for dylib transport behavior and a smoke test for new message wire-ID/class mappings.

Reviewed changes

Copilot reviewed 12 out of 12 changed files in this pull request and generated 3 comments.

Show a summary per file
FileDescription
tests/test_message_signing_protocol_bindings.pyAdds a pure-Python smoke test for new message IDs and mapping registration.
tests/test_dylib_screenshot.pyAdds dylib-only regression tests for screenshot/layout transport behavior.
tests/test_dylib_confirm_flow.pyAdds dylib-only confirm-flow regression coverage scaffolding.
tests/config.pyAdds explicit dylib transport selection and dylib transport wiring for tests.
setup.pyBumps the published package version to 7.14.1.
keepkeylib/transport_dylib.pyIntroduces a new in-process dylib transport implementation over FFI ring buffers.
keepkeylib/messages_tron_pb2.pyRegenerates TRON protobuf bindings to include message-signing message types.
keepkeylib/messages_ton_pb2.pyRegenerates TON protobuf bindings to include message-signing message types.
keepkeylib/messages_solana_pb2.pyRegenerates Solana protobuf bindings to include off-chain message-signing message types.
keepkeylib/messages_pb2.pyUpdates global message-type enums/wire IDs for the new bindings.
keepkeylib/client.pyAdds thin client helpers for the newly introduced Solana, TRON, and TON signing APIs.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment threadtests/test_dylib_confirm_flow.py
Comment on lines +56 to +60
self.client = KeepKeyDebuglinkClient(transport)
self.client.set_debuglink(debug_transport)
# No wipe_device — dylib boot already drew the home screen and
# that's what we want to capture. Going through wipe would also
# exercise confirm_helper, which is intentionally out of scope here.
Comment threadkeepkeylib/client.py Outdated

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 12 out of 12 changed files in this pull request and generated 2 comments.

Comment threadkeepkeylib/transport_dylib.py Outdated
Comment threadtests/config.py
BitHighlanderand others added 2 commits May 9, 2026 17:50
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
@BitHighlander
BitHighlander merged commit fabd6c6 into masterMay 9, 2026
4 checks passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@BitHighlander@pastaghost