Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 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
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
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