Today, Clear Signing — an open standard for making Ethereum transactions readable to the humans who authorize them — moves under the Ethereum Foundation's Trillion Dollar Security Initiative as its neutral steward. The launch ships an updated ERC-7730, an EF-hosted registry mirrorable by anyone, an ERC-8176 attestation framework, the Cyfrin-authored ERC-8213 bytes-level fallback, and developer libraries from a multi-vendor working group.
Cyfrin is one of the contributors. This post is what we built for the launch, why this problem has been a priority for us, and where we go from here.
The release isn't a single project — it's four pieces of infrastructure that work together:
clearsig for Python — covering ERC-7730 translation, ERC-8176 descriptor hashing, ERC-8213 digests, and Safe-specific signing.In February 2025, Bybit's Ethereum cold wallet was drained for roughly $1.5 billion — at the time, the largest cryptocurrency theft on record. The attackers didn't break any cryptography. They didn't bypass any signatures. Multiple authorized signers, holding hardware wallets, approved the transaction that moved the funds. They just didn't realize what they were approving.
Five months earlier, Radiant Capital lost roughly $50 million in a structurally identical incident: hardware-wallet signers clicked "approve" on what their front-end claimed was a routine multisig operation, while their devices actually authorized a delegatecall handing the protocol's logic to an attacker. More recently, Drift Protocol joined the same list.
Different protocols. Different timelines. The same root cause: people authorized transactions they couldn't actually read.
This pattern has a name: blind signing — approving a transaction whose contents you can't verify, on the trust that whatever showed it to you wasn't lying. Hardware wallets were supposed to be the answer; without descriptors, they fall back to hex and the problem persists on a more trustworthy screen.
The working group's slogan is the cleanest summary of the goal: What You See Is What You Sign (WYSIWYS). Until that is the default, and blind signing is the exception rather than the norm, the last line of defense — the human reviewing the transaction — does not hold.
When you authorize a transaction from a hardware wallet, the device receives a hex string — the calldata — and asks you to confirm it. In the worst case, that is all you see:
0x6a76120200000000000000000000000087870bca3f3fd6335c3f4ce8392d69350b4fa4e20000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000084617ba037000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000000f42400000000000000000000000009467919138e36f0252886519f34a0f8016ddb3a30000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
Reading that requires you to:
A skilled engineer can do all of this with tools, given time, at a workstation. A multisig signer, under time pressure, on a three-line OLED display, cannot.
The industry's first response was to stop showing hex to humans and show "Send 100 USDC to vitalik.eth" instead. Making that version actually safe — rather than merely friendlier — is harder than it looks.
You might think the ABI solves the problem. Given the ABI, you can decode the bytes above into execTransaction(0x87870Bca..., 0, 0x617ba037..., 0, ...) and display each parameter. Two reasons that's still not enough.
Function names lie. The ABI tells you a function is called transfer. It doesn't tell you what transfer actually does. Solidity lets the developer name a function anything. Nothing stops a contract from naming a withdrawal function deposit, a malicious upgrade function pause, or a fund-extraction routine claimRewards. If your wallet trusts the function name to derive user intent, anyone deploying a contract can choose any intent for your wallet to display.
Parameters need context to be meaningful. A uint256 amount is just a number. Wei? Micro-USDC with 6 decimals? A timestamp? A duration in seconds? A basis-point fee? Without protocol-specific knowledge baked in, "amount: 1000000" is not actually human-readable — it's just a less alarming version of hex. ABIs give you types, not semantics.
Decoding the ABI gives you a more readable version of the hex. It does not give you a truthful version. That distinction is what gets people robbed.
For a deeper walkthrough of why this is hard, Patrick recorded an explainer a while back: Hardware wallet clear signing.
ERC-7730 — originated by Ledger and matured into a multi-vendor standard — addresses both problems above. It defines a JSON format (a descriptor) that maps a contract's functions to human-readable intents and field-rendering instructions. A USDC descriptor's transfer entry:
"transfer(address to,uint256 value)": {
"intent": "Send",
"fields": [
{ "path": "to", "label": "To", "format": "addressName" },
{ "path": "value", "label": "Amount", "format": "tokenAmount" }
]
}
A wallet with this descriptor knows: when you call transfer on USDC, show "Send {amount} to {recipient}", format the amount with six decimals and the USDC ticker, resolve the recipient to an ENS name if available.
Descriptors are curated by the protocol team, reviewed by third-party auditors, and collected in a neutral registry hosted by the EF. Wallets pull from the registry and decide whose attestations they trust. The function-name and parameter-context problems are addressed.
clearsig translateCyfrin's first contribution is clearsig — an open-source Python CLI and library that implements ERC-7730 translation, ERC-8176 descriptor hashing, ERC-8213 digests, and Safe-specific hashing. It complements the official TypeScript and Rust libraries shipping in today's launch.
The cleanest way to feel what clear signing buys you is to run a real transaction through it. Here is a Safe execTransaction wrapping an Aave v3 supply of 1 USDC — the same nested shape as the Bybit attack.
Without clear signing, the hardware wallet sees:
0x6a76120200000000000000000000000087870bca3f3fd6335c3f4ce8392d69350b4fa4e20000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000084617ba037000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000000f42400000000000000000000000009467919138e36f0252886519f34a0f8016ddb3a30000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
Same bytes, decoded through ERC-7730:
clearsig translate \
0x6a76120200000000000000000000000087870bca3f3fd6335c3f4ce8392d69350b4fa4e20000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000084617ba037000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000000f42400000000000000000000000009467919138e36f0252886519f34a0f8016ddb3a30000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 \
--to 0x41675C099F32341bf84BFc5382aF534df5C7461a \
--chain-id 1 \
--from-address 0x9467919138E36f0252886519f34a0f8016dDb3a3
Intent: sign multisig operation (Safe{Wallet})
Function: execTransaction(address,uint256,bytes,uint8,uint256,uint256,uint256,address,address,bytes)
Operation type: Call
From Safe: 0x41675C099F32341bf84BFc5382aF534df5C7461a
Execution signer: 0x9467919138E36f0252886519f34a0f8016dDb3a3
Transaction: Supply (Aave DAO)
-> supply(address,uint256,address,uint16)
Amount to supply: 1000000
Collateral recipient: 0x9467919138e36f0252886519f34a0f8016ddb3a3
Referral Code: 0
Gas amount: 0
Gas price: 0
Gas receiver: 0x0000000000000000000000000000000000000000
Safe Tx Gas: 0
Signatures: 0x
The outer execTransaction is identified as a Safe multisig operation; the inner calldata is recognized as Aave's supply, with the amount and recipient pulled out by name. A signer looking at the second version can tell what they are approving. A signer looking at the first version cannot.
clearsig translate runs this offline against a local copy of the registry, so signers — and auditors — can verify on an isolated device before approving on the hardware wallet itself. Install it with whichever Python tool you prefer:
uv tool install clearsig # or pipx / pip
clearsig translate <calldata> --to <address> --chain-id <N>
The first run auto-downloads the ERC-7730 registry. Point at a local checkout — for instance a PR branch under review — with ERC7730_REGISTRY_PATH=/path/to/registry.
Descriptors only exist for contracts someone has authored a descriptor for. For everything else — brand-new contracts, custom packed encodings (Safe MultiSend, Uniswap Universal Router), zkSync L1 ↔ L2 system messages — there is no descriptor to render. For some of these there never will be, because the encoding does not fit the selector-plus-ABI model ERC-7730 assumes.
ERC-8213, which Cyfrin authored, exists for those cases. It defines short, reproducible cryptographic fingerprints of exactly what is about to be signed. The simplest is the calldata digest:
keccak256(uint256(len(calldata)) || calldata)
A length-prefixed hash of the exact bytes that will hit the contract. The use case is cross-device verification: before signing on a hardware wallet, the signer computes the same digest on an isolated machine — a clean laptop, an offline phone, a second hardware wallet. If the digest the wallet is about to sign matches the digest computed independently, the bytes are correct. If they don't, something has been substituted between the front-end and the device — exactly the class of attack that drained Bybit and Radiant.
ERC-8213 covers calldata digests, EIP-712 domain / message / final hashes, and Safe-specific signing hashes. It doesn't try to make the data human-readable — that's ERC-7730's role. It makes it verifiable, which is a weaker but always-applicable guarantee.
We built erc8213.eth.limo as a teaching site for using these digests on hardware wallets — it also tracks which wallets have implemented the standard. The first is Keycard Shell, which displays the calldata digest alongside the ABI-decoded view during signing, so a security-conscious signer can verify in a single hash instead of paging through hex. An older walkthrough of the same pattern for Safe wallets, before ERC-8213 was formalized, is at hardware-wallet-multi-sig-signature-verification-how.
Two real limitations remain, and we don't want to oversell what shipped today.
Descriptors aren't instant. If a contract isn't in the registry yet, there's no human-readable rendering — you're back to ABI decoding or hex. Getting included takes a PR, an auditor review, and ecosystem buy-in. ERC-8213 is the fallback for that window (and for contracts that never fit the ERC-7730 model at all), but it isn't a replacement for descriptor coverage.
Air-gapped wallets need a transport story. Air-gapping a hardware wallet — keeping it physically disconnected from the host — is one of the strongest security postures available, but it makes pulling a large, frequently-updated registry hard. The working group is building a Wire Protocol to address this; it isn't live yet. Until it is, descriptor support on air-gapped devices will lag online ones.
clearsig in fullclearsig packages everything above into one CLI and Python library:
# What does this calldata mean? (ERC-7730 translation)
clearsig translate <calldata> --to <address> --chain-id 1
# What digest will my hardware wallet display before signing? (ERC-8213)
clearsig calldata-digest <calldata>
# EIP-712 typed data — domain, message, and final hash
clearsig eip712 message.json
# Safe transaction hashes (offline) — matches safe-hash-rs byte-for-byte
clearsig safe-hash --chain-id 1 --safe-address 0x... --safe-version 1.4.1 ...
# Safe off-chain messages (e.g. OpenSea sign-in)
clearsig safe-msg --chain-id 1 --safe-address 0x... --message-file msg.txt
# Hash an ERC-7730 descriptor for ERC-8176 attestations
clearsig descriptor-hash registry/<project>/<descriptor>.json
# Bootstrap a starter descriptor for a new contract from Sourcify
clearsig generate --chain-id 1 --to 0x... --owner USDC
# Encode and decode calldata against a signature (offline)
clearsig calldata "approve(address,uint256)" 0x05C54380408aB9c31157B7563138F798f7826aA0 1
clearsig calldata-decode "approve(address,uint256)" 0x095ea7b3...
# Function-selector primitives
clearsig sig "approve(address,uint256)" # → 0x095ea7b3
clearsig keccak "approve(address,uint256)" # full keccak256
clearsig 4byte 0x095ea7b3 # reverse-lookup via 4byte.directory
Everything except 4byte runs offline against a local copy of the ERC-7730 registry, so signers can verify on an air-gapped machine. The hashing code is cross-checked byte-for-byte against viem for EIP-712 and safe-hash-rs for Safe hashes, so the digests clearsig produces are the digests the rest of the ecosystem agrees on. clearsig generate uses Sourcify — auto-traversing proxies to the implementation — to bootstrap descriptors from verified ABIs.
The full surface, with worked examples for Safe multisig, Aave, zkSync, and Universal Router edge cases, is in the clearsig README.
Clear signing is one of the most leveraged places to spend security effort in the next few years. The gap between a $1.5 billion exploit and a non-event is often whether the signers could read the bytes in front of them. Cyfrin's position in this fight is deliberate: we don't ship a hardware wallet, and we have no economic interest in any particular one winning. That's exactly what makes us useful as a descriptor auditor — wallets and protocols can attest to our reviews without picking a side. Specifically, Cyfrin is committing to:
clearsig, ERC-8213, and the broader clear-signing toolkit. Alongside clearsig, we maintain safe-hash-rs (offline Safe transaction hashing), chain-tools (calldata decoding and other signing utilities), and the Wise Signer Snap for MetaMask — which will be picking up ERC-8213 support shortly. All MIT-licensed; an index lives at docs.cyfrin.io.The launch wouldn't be here without years of work from the ecosystem. Specifically:
clearsig generate produces a starting point from a verified ABI; refine intents and labels by hand.clearsig translate and clearsig calldata-digest are designed exactly for this.erc-7730.mdclearsig repository: github.com/Cyfrin/clearsig