Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
3d20db3
report: 10 fixes to the 7.15 PDF test report
BitHighlander Jul 3, 2026
5307888
clearsign v2: static-schema serializer, tests, and PDF report coverage
BitHighlander Jul 3, 2026
4ceec46
fix(tron): gate legacy dummy-raw_data tests behind AdvancedMode
BitHighlander Jul 6, 2026
99f1e06
fix(solana): split versioned-v0 test into static-verified and ALT-opa…
BitHighlander Jul 6, 2026
3b8418b
report: 10 fixes to the 7.15 PDF test report — release-gate coverage …
BitHighlander Jul 3, 2026
c101137
fix(clearsign): shorten erc4337 innerCall disclosure to fit 32-byte v…
BitHighlander Jul 3, 2026
1674346
Merge fix/pdf-report-coverage-gaps-v3: v2 static-schema harness into …
BitHighlander Jul 7, 2026
e0587c0
test(solana): gate versioned-v0 tests on requires_firmware(7.15.0)
BitHighlander Jul 7, 2026
4c3f158
test(ton): enable AdvancedMode for TonSignTx tests
BitHighlander Jul 8, 2026
560b897
test(mayachain): use the real Maya ETH Router v4 in eth swap/liquidit…
BitHighlander Jul 8, 2026
02fa3ea
test(hive): negative coverage for SLIP-48 path enforcement + memo limit
BitHighlander Jul 15, 2026
35555d7
test(hive): role-exact path enforcement per operation
BitHighlander Jul 15, 2026
8e0607f
feat(hive): sign_message (HiveSignMessage 1614/1615) + signBuffer con…
BitHighlander Jul 15, 2026
591dba8
test(hive): drop msg arg from assertEqual (KeepKeyTest override takes…
BitHighlander Jul 15, 2026
ed8cbdf
test(hive): review follow-ups — owner' rejected for signBuffer, >128B…
BitHighlander Jul 15, 2026
a4332c7
feat(hive): sign_operations (HiveSignOperations 1616/1617) + parsed-o…
BitHighlander Jul 15, 2026
15d95ec
test(hive): oversize tx must actually exceed the 2048 proto cap
BitHighlander Jul 15, 2026
a322ef1
fix(review): regenerate bindings for the post-#36 protocol; verify Ma…
BitHighlander Jul 16, 2026
03af8d1
fix(review): reference decoder must reject 0x80 like firmware; pin co…
BitHighlander Jul 16, 2026
25df24c
test(clearsign): make the icon reference decoder exact, like the firm…
BitHighlander Jul 16, 2026
c475482
fix(review): correct the public icon contract; fix the Maya recovery …
BitHighlander Jul 16, 2026
7231374
test(hive): expect non-printable messages to be rejected
BitHighlander Jul 17, 2026
78bd6ca
test: unchecked SPL transfer/approve require AdvancedMode; native ETH…
BitHighlander Jul 17, 2026
536bfb6
test(solana): CreateAccount/SetAuthority require AdvancedMode; StakeA…
BitHighlander Jul 17, 2026
71d6086
test(maya): un-skip native signtx — digest-verified, no frozen vector…
BitHighlander Jul 18, 2026
d39ab36
test(thorchain): Avalanche router deposit clear-signs; unpinned chain…
BitHighlander Jul 18, 2026
8586652
chore(submodule): declare device-protocol from keepkey/up-release-pro…
BitHighlander Jul 18, 2026
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[submodule "device-protocol"]
path = device-protocol
url = https://github.com/BitHighlander/device-protocol.git
branch = master
url = https://github.com/keepkey/device-protocol.git
branch = up/release-protocol
[submodule "keepkeylib/eth/ethereum-lists"]
path = keepkeylib/eth/ethereum-lists
url = https://github.com/keepkey/ethereum-lists.git
Expand Down
10 changes: 7 additions & 3 deletions keepkeylib/clearsign_catalog.py
Original file line number Diff line number Diff line change
Expand Up @@ -954,10 +954,14 @@ def _bytes_tail(b):
[{'name': 'sender', 'format': ARG_FORMAT_ADDRESS, 'value': addr('0x9406Cc6185a346906296840746125a0E44976454')},
{'name': 'nonce', 'format': ARG_FORMAT_STRING, 'value': b'UserOperation nonce: 12'},
{'name': 'beneficiary', 'format': ARG_FORMAT_ADDRESS, 'value': addr('0x' + '43' * 20)},
{'name': 'innerCall', 'format': ARG_FORMAT_STRING, 'value': b'decoded separately, not raw'}],
{'name': 'innerCall', 'format': ARG_FORMAT_STRING, 'value': b'empty (representative)'}],
why='A bundler-submitted meta-tx: the EntryPoint singleton validates and executes a batch of '
'smart-account operations; the inner callData (what the smart account will actually do) '
'must be decoded and shown, never left as an opaque blob one layer inside another.',
'smart-account operations. KNOWN GAP, disclosed: this representative UserOp carries an '
'EMPTY inner callData (the array-of-dynamic-tuples nesting is beyond the current static '
'ABI encoder), so this flow proves sender/nonce/beneficiary are decoded but does NOT '
'prove the inner callData — what the smart account will actually do — is decoded. A real '
'UserOp with non-empty callData would need it decoded and shown, never left as an opaque '
'blob one layer inside another; that inner-decode capability is future work.',
source='https://etherscan.io/address/0x0000000071727De22E5E9d8BAf0edAc6f37da032 (EntryPoint v0.7)',
),
)
Expand Down
51 changes: 46 additions & 5 deletions keepkeylib/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -711,16 +711,57 @@ def ethereum_send_tx_metadata(self, signed_payload, metadata_version, key_id):
return self.call(msg)

@expect(proto.Success)
def load_clearsign_signer(self, key_id, pubkey, alias):
"""Load a runtime clearsign signer (compressed pubkey + alias) into a
key slot. Triggers a mandatory on-device confirmation; RAM-only, the
signer is gone on reboot. Metadata verified by a loaded signer shows
a warning screen naming the alias before every clearsign page."""
def load_clearsign_signer(self, key_id, pubkey, alias, icon=None,
icon_width=None, icon_height=None, persist=None):
"""Load a clearsign signer (compressed pubkey + alias) into a key slot.
Triggers a mandatory on-device confirmation. Metadata verified by a
loaded signer shows a warning screen naming the alias before every
clearsign page.

icon (optional, <= 384 bytes) is an identity logo shown on the trust
screen. It is RUN-LENGTH ENCODED with byte-valued pixels, NOT a packed
bitmap: draw_bitmap_mono_rle() in keepkey-firmware lib/board/draw.c is
the decoder of record, and it is what every bundled image already uses.

Grammar -- read n = int8(data[i++]):
n in [1, 127] RUN : one value byte follows; emit it n times.
n in [-127, -1] LITERAL : (-n) value bytes follow; emit each once.
n == 0 : invalid.
n == -128 (0x80) : INVALID -- the device's run counter is
int8_t and cannot represent 128. Split a
128-byte literal into two packets.
The stream must decode EXACTLY: no run may straddle the end of the
image, exactly icon_width*icon_height pixels are emitted (row-major),
and the whole input must be consumed -- trailing packets are rejected.
The device validates this before showing or storing the icon. See
LoadClearsignSigner.icon in messages-ethereum.proto for the grammar and
a golden vector.

icon_width and icon_height are required with icon.
icon_width : 1..40 -- the confirm screen's icon column
(LEFT_MARGIN_WITH_ICON). Text begins at x=40 and the
icon is drawn after it, so a wider icon would paint over
the alias, fingerprint and the "NOT verified by KeepKey"
warning. Capped, not clipped.
icon_height : 1..64 -- the icon column is 64px tall.
Omit all three for a text-only identity.

persist=True also writes the identity to flash, so it survives reboot
and is reloaded automatically. The default is RAM-only (gone on
reboot)."""
msg = eth_proto.LoadClearsignSigner(
key_id=key_id,
pubkey=pubkey,
alias=alias,
)
if icon is not None:
msg.icon = icon
if icon_width is not None:
msg.icon_width = icon_width
if icon_height is not None:
msg.icon_height = icon_height
if persist is not None:
msg.persist = persist
return self.call(msg)

@session
Expand Down
18 changes: 18 additions & 0 deletions keepkeylib/hive.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,24 @@ def sign_tx(client, address_n, chain_id, ref_block_num, ref_block_prefix,
}))


def sign_message(client, address_n, message):
"""Keychain signBuffer contract: sig over SHA256(raw message bytes) only —
no chain_id prepend, no message prefix."""
if isinstance(message, str):
message = message.encode('utf-8')
return client.call(proto.HiveSignMessage(address_n=address_n, message=message))


def sign_operations(client, address_n, serialized_tx, chain_id=None):
"""Sign a host-serialized Graphene transaction (HiveSignOperations).
Firmware parses the bytes and clear-signs the phase-1 op table
(vote, comment, custom_json); digest = SHA256(chain_id || tx)."""
kwargs = dict(address_n=address_n, serialized_tx=serialized_tx)
if chain_id is not None:
kwargs['chain_id'] = chain_id
return client.call(proto.HiveSignOperations(**kwargs))


def sign_account_create(client, address_n, chain_id, ref_block_num, ref_block_prefix,
expiration, creator, new_account_name, fee_amount=3000,
owner_key='', active_key='', posting_key='', memo_key=''):
Expand Down
5 changes: 5 additions & 0 deletions keepkeylib/mapping.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,11 @@ def check_missing():
1607: ('HiveSignedAccountCreate', hive_proto),
1608: ('HiveSignAccountUpdate', hive_proto),
1609: ('HiveSignedAccountUpdate', hive_proto),
# 1610-1613 reserved: NEAR
1614: ('HiveSignMessage', hive_proto),
1615: ('HiveSignedMessage', hive_proto),
1616: ('HiveSignOperations', hive_proto),
1617: ('HiveSignedOperations', hive_proto),
}
for wire_id, (msg_name, mod) in _hive_wire_ids.items():
msg_class = getattr(mod, msg_name, None)
Expand Down
58 changes: 43 additions & 15 deletions keepkeylib/messages_ethereum_pb2.py

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading