Stealth Addresses
Specter implements an ERC-5564 compatible stealth address system for private, one-time recipient addresses. Stealth addresses allow a sender to create a fresh, unlinkable address for every transfer — ensuring that on-chain observers cannot determine who received a payment, even when the sender and recipient are known entities.
This system complements the Ghost Protocol. Where the Ghost Protocol provides privacy through commit/reveal unlinkability (vanish and summon are unconnectable), stealth addresses provide privacy at the transfer layer — hiding the recipient of a direct on-chain token transfer.
How Stealth Addresses Work
The stealth address scheme uses Elliptic Curve Diffie-Hellman (ECDH) key agreement on the secp256k1 curve. The recipient publishes a meta-address (a pair of public keys) once, and senders derive unique one-time addresses from it without any interaction.
Key Components
| Component | Description |
|---|---|
Spending key (p_spend) | Recipient's long-term private key for claiming funds |
Viewing key (p_view) | Recipient's private key for scanning and identifying incoming transfers |
| Meta-address | Public keys (P_spend, P_view) published once by the recipient |
| Ephemeral keypair | One-time keypair (r, R) generated by the sender for each transfer |
| Stealth address | One-time address derived from ECDH shared secret |
| View tag | Single-byte hint for efficient scanning |
Protocol Flow
Step-by-Step Breakdown
1. Sender generates an ephemeral keypair
The sender creates a random scalar r and computes the corresponding public point R = r * G, where G is the secp256k1 generator. This keypair is used only once and discarded after the transfer.
2. Sender computes the one-time stealth address via ECDH
Using the recipient's published meta-address (P_spend, P_view), the sender computes a shared secret:
s = r * P_view
The stealth public key is derived as:
P_stealth = P_spend + hash(s) * G
The stealth address is the Ethereum-style address of P_stealth. This address has never appeared on-chain before and is unlinkable to the recipient's known addresses.
3. Sender calls GhostStealthAnnouncer
The sender invokes the GhostStealthAnnouncer contract, which:
- Transfers tokens to the computed stealth address.
- Records the transfer metadata — the ephemeral public key
Rand a view tag (first byte ofhash(s)) — in an on-chain storage array.
4. Recipient scans the on-chain transfers array
The recipient periodically reads the transfers array from the GhostStealthAnnouncer contract. For each entry:
- View tag pre-filtering: The recipient computes
hash(p_view * R_entry)and checks if the first byte matches the stored view tag. This is a cheap check that filters out ~99.6% of non-matching transfers without performing the full ECDH computation. - Full ECDH verification: For entries that pass the view tag filter, the recipient performs the complete shared secret derivation and verifies the stealth address matches.
5. Recipient derives the stealth private key and claims funds
Once a matching transfer is identified, the recipient derives the stealth private key:
p_stealth = p_spend + hash(s)
This private key controls the stealth address. The recipient signs a transaction with it to claim the funds.
On-Chain Array Storage
A standard ERC-5564 implementation on Ethereum uses event logs for the announcement mechanism — the sender emits an event containing the ephemeral public key and view tag, and the recipient scans event logs using eth_getLogs.
Specter uses on-chain array storage instead of event logs. This is a deliberate design choice driven by a limitation of the Cosmos EVM environment:
| Approach | Ethereum | Specter (Cosmos EVM) |
|---|---|---|
| Announcement mechanism | Event logs | Storage array |
| Scanning method | eth_getLogs RPC | Direct storage reads |
| Why | Efficient log indexing available | eth_getLogs not reliably supported in Cosmos EVM |
| Trade-off | Lower storage cost, index-dependent | Higher storage cost, no indexer dependency |
The GhostStealthAnnouncer contract maintains a transfers array in contract storage. Each entry contains:
struct StealthTransfer {
address ephemeralPubKeyAddr; // Compressed ephemeral public key
uint8 viewTag; // First byte of ECDH shared secret hash
address stealthAddress; // Derived one-time address
uint256 amount; // Transfer amount
}
Recipients scan this array directly via eth_call (read-only RPC) without needing a log indexer.
View Tag Optimization
Scanning all on-chain transfers requires an ECDH computation per entry, which becomes expensive as the transfer count grows. The view tag provides a 1-byte probabilistic filter:
The view tag eliminates 255/256 (~99.6%) of non-matching entries from requiring full ECDH computation, reducing scanning cost by approximately two orders of magnitude.
Integration with the Ghost Protocol
Stealth addresses and the Ghost Protocol provide complementary privacy guarantees. They can be composed for layered privacy:
Flow: A sender transfers tokens to a stealth address (hiding the recipient). The recipient claims the stealth funds and immediately vanishes them into the Ghost Protocol (hiding the amount and breaking the transaction graph). Later, the recipient summons the value to any address (hiding the link between claim and spend).
This composition provides:
- Recipient privacy — the stealth address hides who received the initial transfer.
- Amount privacy — the vanish operation commits the value without revealing it.
- Graph privacy — the summon operation is unlinkable to the vanish, breaking the transaction trail.
Privacy Properties
| Property | Guarantee |
|---|---|
| Recipient unlinkability | Each transfer uses a unique one-time address; no on-chain link to recipient's known addresses |
| Sender privacy | Sender's address is visible on-chain (stealth addresses hide recipients, not senders) |
| Transfer amount | Visible on-chain in the current implementation |
| Ephemeral key reuse | Each transfer MUST use a fresh ephemeral keypair; reuse breaks recipient privacy |
| Meta-address publication | Published once; does not reveal spending key or viewing key |
| Scanning privacy | Only the viewing key holder can identify their transfers; requires reading on-chain data |
Security Considerations
- Ephemeral key uniqueness: If a sender reuses an ephemeral keypair
(r, R)for two transfers to different recipients, both recipients can compute the same shared secret. This does not directly compromise funds but leaks the fact that both transfers came from the same sender with the same ephemeral randomness. Clients MUST generate a fresh ephemeral keypair per transfer. - View tag collisions: The 1-byte view tag produces false positives at a rate of ~1/256. This is a performance optimization, not a security mechanism. False positives are resolved by the full ECDH check.
- Scanning cost: As the transfers array grows, scanning cost increases linearly. Future optimizations may include sharded announcement contracts or off-chain indexing with Merkle proofs of inclusion.