
A signature is a statement, but only if you can read it. When a wallet cannot decode a payload it falls back to showing a hash and asks for approval, and a hash cannot be checked against intent. That fallback is the quiet path around every other control in self custody: the key stays in the secure element, the device is genuine, the firmware is signed, and the transaction still sends everything to an address you have never seen.
What the device is actually asked to do
Signing produces a signature over a digest. The digest is a fixed length value derived from the message, and the device’s real work is deciding what the message says before it signs.
Firmware carries decoders for common payloads. A plain transfer, a known token contract, a familiar staking call: each has a template, so the screen can show an amount and an address. When the payload matches no template, the device has two options. It can refuse, or it can show the digest and let you decide.
A hash is not consent
A hash is a fingerprint, not a sentence. You can compare it with another hash, which is useful when you built the transaction yourself on an offline machine and can check both ends. You cannot compare it with an intention.
Consider what a blind signed digest can carry. An unlimited token approval. An operator approval over an entire NFT collection. A permit that lets a third party move a balance at any time. A call to a proxy that forwards to something else. None of that is visible in a digest, and each one is irreversible once the transaction confirms.
The mental trap is the word approve. Pressing a button labelled approve on a device feels like approving a specific action. It is approving a signature over bytes you have not read.
Why the wallet cannot always decode
Decoding is hard because calldata is open ended. Any contract can define any function, and new contracts appear faster than firmware updates. Proxies and delegate calls add a layer: the call names one contract and executes another.
There is a legitimate use for the fallback. Recovery flows, unusual script paths and some multisig coordination produce payloads a consumer device will never have a template for. Those are exactly the cases where the user built the transaction deliberately and can verify the digest against a second source.
The illegitimate use is everything else, and it is the common case. A hostile site does not need a novel exploit. It needs a payload the device cannot render.
The screen test
Three questions decide whether a wallet is usable for signing. Does it show the amount in the asset, with the right decimals? Does it show the full destination, including the middle characters? Does it name the function when the payload is not a plain transfer?
If the answer to all three is yes, the device is doing the only job that matters at the moment of signing. If any answer is no, the hardware is protecting a key you are about to spend without reading the instruction.
Truncated addresses are the place this bites most often, and the habit that catches them is in the address poisoning note. Chain choice changes the stakes rather than the principle. Ethereum.org’s L2 directory put Base at $14.6B of value in October 2026, and every position in that figure moves by a signature someone had to read.
| Payload | Can a screen render it | What a hash-only fallback hides |
|---|---|---|
| Native transfer | Yes | Amount and destination |
| Token transfer | Yes, with decimals | Which token contract |
| Unlimited approval | Yes, if the wallet decodes approve | The spender, and the word unlimited |
| Permit signature | Often not | That no gas is being spent |
| Proxy call | Sometimes | The contract that actually executes |
| Batch or multicall | Rarely | Every action inside the batch |
Refusing is a control
The strongest setting is a device that will not sign an undecoded payload at all. The setting exists on some devices and is off by default on many, because it breaks edge cases.
Turning it on converts an unreadable request into a stop. The transaction fails, you go and find out what it was, and the default answer becomes no. For a savings device that is the right default. For a device used daily the friction may be too high, which is an argument for separating the two.
What to do next
Enable strict decoding on the device that holds savings, and accept that some transactions will fail. Keep a second, smaller setup for the experiments that need the fallback.
Read the screen, not the companion app. The app is software on a general purpose computer, and the device exists because that computer is not trustworthy. If the two disagree, the device is right and the transaction is wrong.
Then re-test the habit after every firmware update. Decoders change, defaults change, and a setting enabled two years ago may not have survived. The limits of account abstraction explain why a smarter account does not remove this step.
0 comments
No comments yet — ask a question and we will answer it here.