Repo Explainer: `stealth-addresses` and the One-Byte Trick Behind Private Payments
A Solidity implementation of ERC-5564 and ERC-6538 that makes stealth addresses usable by shrinking wallet scanning to a tiny filter, then doing the heavy cryptography only when a match looks real.
- `stealth-addresses` treats privacy as a discovery problem first, then uses a one-byte view tag to make that discovery cheap.
- The repo turns ERC-5564's math into Solidity, so stealth payments are not just a standard on paper but a wallet workflow that can be implemented.
- Its contracts separate announcement, validation, and registry duties, which keeps the privacy primitive modular enough for real integrations.
- Stealth addresses sit in the middle of the privacy stack, lighter than mixers or zero-knowledge systems, but far more usable than a hand-wavy concept.
Privacy is a scan problem
Public chains make every payment legible by default. That is the obvious problem. The less obvious one is discovery: if a wallet has to inspect every announcement to see whether a payment belongs to you, privacy becomes expensive, slow, and easy to ignore.
That is the opening move in stealth-addresses. The repository does not treat the stealth address as the hard part. It treats the scan loop as the hard part, and the one-byte view tag is the answer. Most announcements can be rejected with almost no work, which is why this approach feels like infrastructure instead of a demo.
How stealth-addresses turns a meta-address into a spendable output
The sender starts from a stealth meta-address, which bundles the recipient's spending public key and viewing public key. The sender picks an ephemeral key, derives a shared secret with the viewing key, and hashes that secret into two things: a view tag for quick filtering and a tweak for the final address derivation. The recipient can later reproduce the same path with their viewing key, then claim the funds with the spending key.
// Simplified sketch of the ERC-5564 derivation
bytes32 sharedSecret = keccak256(abi.encodePacked(
ecdh(ephemeralPrivKey, recipientViewingPubKey)
));
bytes1 viewTag = bytes1(sharedSecret);
bytes32 tweak = keccak256(abi.encodePacked(sharedSecret, metadata));
// P_stealth = P_spend + H(e * P_view) * G
address stealthAddress = deriveAddress(recipientSpendingPubKey, tweak);
That formula is the heart of the repo. The sender never reuses the recipient's static address, but the recipient can still recognize the output later because the same shared secret lands on both sides. The result is unlinkability without a new consensus rule, which is why the implementation matters more than the slogan.
The two-contract wrapper around the math
The math lives in StealthAddresses.sol, but the user-facing protocol is split into contracts that do different jobs. ERC5564Announcer emits the announcement event with the ephemeral public key and metadata, ERC5564AnnouncerWithHooks adds curve-point validation before the log is written, and ERC6538Registry maps a user to a stealth meta-address with signed registration flows. That separation is the product decision hiding inside the cryptography.
The registry supports relayed registration through signed messages, so a user can authorize a third party to publish keys without giving up control. The announcer keeps broadcast mechanics simple. The hook-based variant makes malformed inputs harder to spam into the system.
Stealth addresses, defined under the ERC-5564 standard, are a specialized method for generating one-time, unlinkable addresses for on-chain payments. Instead of a recipient publishing a static address that everyone uses, they publish a "stealth meta-address." Senders then use this meta-address to derive a fresh, unique Ethereum address for each transaction.
Why this implementation matters as a standards piece
This repo is not inventing a brand-new privacy model. It is making ERC-5564 and ERC-6538 concrete in Solidity 0.8.33, with Foundry as the build and test harness and frost-secp256k1-evm supplying the low-level curve operations the EVM does not normally expose. That matters because on-chain secp256k1 math is unusual, and most privacy systems push this logic off-chain or into heavier proof systems.
The implementation choice says a lot. By keeping the protocol legible in Solidity, the repo makes it easier for wallet teams, auditors, and protocol designers to reason about the trust boundary. It is less ambitious than full shielded transactions, but far easier to slot into existing Ethereum-style payment flows.
Where stealth addresses sit in the privacy stack
Stealth addresses are not trying to solve every privacy problem at once. They hide recipient linkage, not the full transaction graph. That makes them useful in a different way than mixers or zero-knowledge systems, which aim at a broader concealment but come with heavier operational and cryptographic costs.
| Approach | What it optimizes for | Main trade-off |
|---|---|---|
| Stealth addresses | Recipient unlinkability with cheap scanning | Does not hide amount or sender by itself |
| Mixers | Breaking source-destination links through pooled liquidity | Needs liquidity and adds operational friction |
| Zero-knowledge privacy systems | Hiding sender, recipient, and often amount | Heavier proving and verification complexity |
That is the real niche of stealth-addresses. It makes private receiving practical enough to fit normal wallets, while staying narrow enough to implement, audit, and integrate. In a market crowded with privacy rhetoric, that kind of specificity is the point.