Skip to content

feat(emu): expand the confirm dialog to cover the Hive operations#374

Merged
BitHighlander merged 2 commits into
developfrom
feat/emu-hive-confirm-details
Jul 20, 2026
Merged

feat(emu): expand the confirm dialog to cover the Hive operations#374
BitHighlander merged 2 commits into
developfrom
feat/emu-hive-confirm-details

Conversation

@BitHighlander

Copy link
Copy Markdown
Collaborator

The gap

Every chain family has a *ConfirmDetails() expander that turns a signing payload into the emulator's confirm dialog — evmConfirmDetails, cosmosConfirmDetails. Hive had none.

/hive/sign-operations hands the device a pre-serialized Graphene blob, so unlike every other chain the dialog has nothing to read an amount or counterparty out of. It passed a bare { operation: 'hiveSignOperations', chain: 'HIVE' }:

Hive Sign Operations
Chain: HIVE
[Confirm]  [Reject]

…for a transaction that may carry up to four distinct operations. Claiming rewards, cancelling a market order and powering down all produced the same prompt. /hive/sign-message was equally bare — no indication which login message a dApp had asked to sign.

The change

before after
claim rewards Hive Sign Operations Claim Rewards · 1.500 HIVE + 1000.000000 VESTS
market order Hive Sign Operations Market Order · 1.500 HIVE → 0.400 HBD
delegation Hive Sign Operations Delegate HP · Delegatee: @bob · 1000.000000 VESTS
batch Hive Sign Operations 2 Hive operations · Post / Comment · Payout Options
signBuffer Hive Sign Message Sign Message · {login: "bithighlander22"}

hiveConfirmDetails() expands the condenser-style op array the caller already serialized, following evmConfirmDetails. Display only — it never feeds the serializer.

Honesty contract

This module's stated job is "not to MISREPRESENT," so the expander declines to assert what it doesn't know:

  • No forged To: row. claim_reward_balance, limit_order_cancel and account_update2 act on the signer's own account. A recipient row there would be a claim the operation does not make.
  • A self power-up (to: "") says self, not an empty row.
  • Zero rewards are dropped from a claim, so the real amount isn't buried beside 0.000 HBD.
  • A batch never claims one recipient for four operations — it names the count and lists the ops.
  • An unparseable op array falls back to the bare header rather than guessing. The OLED is still authoritative.

hiveMessagePreview() round-trips the UTF-8 decode, so a binary Buffer payload reports as 4 bytes (binary) instead of rendering as mojibake.

Multi-screen behaviour

claim_reward_balance is 2 screens, limit_order_create 2, comment_options 2 + one per beneficiary. One prompt fires per firmware ButtonRequest, so the same summary shows on each press. Intended — the OLED behind it is what advances.

Scope: this does NOT fix the wedge

Worth being explicit, because the two are related. A confirm that is never answered leaves confirm_helper blocked on the poll thread, which wedges the emulator: every subsequent request, on every chain, then fails with Malformed tiny packet until the emulator is force-quit. That was observed in the field — an EVM sign and a Hive signBuffer both failing that way, cleared by restarting the emulator.

An uninformative prompt makes that wedge more likely to be reached, which is why this is worth landing. But it is not the fix. Cancel is one of the legal tiny messages (msg_tiny's static_asserts name it), so the vault can unwedge the device itself instead of requiring a force-quit. That belongs in its own change.

Verification

  • 11 new tests in emulator-confirm-details.test.ts.
  • 129 pass across the deterministic units — this file, txbuilder/hive-ops, __tests__/rest-sign-gating.
  • The live-device integration tests in this suite (address derivation, get-features) vary run to run independently of this change; I saw 17, then 5, then 0 failures across identical runs on unmodified origin/develop.

Branched from origin/develop, so it does not include the three unpushed local commits on that branch.

🤖 Generated with Claude Code

/hive/sign-operations hands the device a pre-serialized Graphene blob, so
unlike every other chain the emulator confirm dialog had no params to read
an amount or a counterparty out of. It passed a bare
{ operation: 'hiveSignOperations', chain: 'HIVE' }, which rendered as

    Hive Sign Operations
    Chain: HIVE
    [Confirm] [Reject]

for a transaction that may carry up to four distinct operations. Claiming
rewards, cancelling a market order and powering down all produced the same
prompt, so the dialog told the user nothing about what they were approving.
/hive/sign-message was equally bare — no indication of which login message a
dApp had asked for.

hiveConfirmDetails() expands the condenser-style op array the caller already
serialized into the existing EmulatorConfirmDetails shape, following
evmConfirmDetails: DISPLAY ONLY, never feeding the serializer. A single op
gets its label, counterparty and amount; a batch names the count and lists
the ops rather than claiming one recipient for four operations.

It keeps this module's honesty contract, so it declines to assert what it
does not know:
  - no "To:" row for claim_reward_balance / limit_order_cancel /
    account_update2, which act on the signer's own account — a recipient
    there would be a claim the operation does not make
  - a self power-up (to: "") says "self" instead of an empty row
  - zero rewards are dropped from a claim so the real amount is not buried
    beside "0.000 HBD"
  - an absent or unparseable op array falls back to the bare header rather
    than guessing; the OLED behind it is still authoritative

hiveMessagePreview() shows the signBuffer payload, round-tripping the UTF-8
decode so a binary Buffer payload is reported as a byte count instead of
rendered as mojibake.

Multi-screen ops (claim_reward_balance is 2 screens, limit_order_create 2,
comment_options 2 + one per beneficiary) raise one prompt per firmware
ButtonRequest, so the same summary shows on each press. That is intended —
the OLED behind it is what advances.

Scope note: this makes the prompt informative, it does NOT add a recovery
path. A confirm that is never answered still leaves confirm_helper blocked
on the poll thread, which wedges the emulator so that every subsequent
request from any chain fails with "Malformed tiny packet" until the emulator
is force-quit. Cancel is a legal tiny message, so the vault can unwedge
itself; that belongs in its own change.

11 new tests; 129 pass across the deterministic units (this file,
txbuilder/hive-ops, rest-sign-gating). The live-device integration tests in
this suite vary run to run independently of this change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… payloads

Two review findings on this PR:

1. toFixed(0) misreported fractional vote weights. Weight is basis points, so
   1bp = 0.01%: a 1 showed as "0%", a 50 (0.5%) as "1%", a -50 as "-1%". The
   dialog was stating a value different from the one being signed. Now keeps
   up to two decimals and drops trailing zeros, so 10000 still reads "100%".

2. hiveMessagePreview's UTF-8 round-trip only catches MALFORMED UTF-8. Bytes
   00 01 02 03, embedded NULs, and bidi/zero-width controls are all valid
   UTF-8, so they passed the check and rendered as invisible or reordered
   "text" — U+202E can make the displayed string differ from the bytes
   signed, which is the exact misrepresentation this module exists to avoid.
   Adds a \p{C} control/format check (tab/newline/CR exempted as ordinary
   whitespace) and treats whitespace-only payloads as binary rather than
   showing an empty dialog.

+6 regression tests covering both. Suite 17/17.
@BitHighlander
BitHighlander merged commit e503737 into develop Jul 20, 2026
1 check passed
Sign up for free to 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.

1 participant