Skip to main content
Stealth addresses let you receive payments at one-time addresses that can’t be linked to your identity. This guide explains the cryptography in plain language.

The Problem

On most blockchains, addresses are public. If you share your address to receive a payment, anyone can see every transaction you’ve received, track your total balance, and link your identity to all your financial activity.

The Solution: Stealth Addresses

Instead of receiving payments at a single reusable address, each payment goes to a fresh one-time address that only you can detect and spend from. Normal vs Stealth

Key Concepts

Stealth Meta-Address

A stealth meta-address is what you publish or share. It contains two public keys:
st:eth:0x{spendingPubKey}{viewingPubKey}
  • Spending public key — used to derive stealth addresses. The private key lets you spend.
  • Viewing public key — used to detect incoming payments. The private key lets you scan.
This two-key design means you can give a third party the viewing key to detect payments on your behalf without giving them the ability to spend.

How a Payment Works

Stealth Payment Flow Step 1: Sender generates a stealth address
import { generateStealthAddress } from "@wraith-protocol/sdk/chains/evm";

const { stealthAddress, ephemeralPubKey, viewTag } = generateStealthAddress(
  recipientSpendingPubKey,
  recipientViewingPubKey
);
// stealthAddress: fresh one-time address
// ephemeralPubKey: publish on-chain
// viewTag: publish on-chain (1 byte for fast filtering)
The sender:
  1. Generates a random ephemeral key pair (r, R)
  2. Computes a shared secret S = r * viewingPubKey (ECDH)
  3. Hashes the secret: h = hash(S)
  4. Computes stealth address = spendingPubKey + h * G
  5. Sends funds to the stealth address
  6. Publishes announcement: (R, viewTag)
Step 2: Announcement The sender publishes an on-chain announcement containing the ephemeral public key R and a view tag. This is public, but reveals nothing about who the recipient is. Step 3: Recipient scans for payments
import { scanAnnouncements } from "@wraith-protocol/sdk/chains/evm";

const matched = scanAnnouncements(
  announcements,
  keys.viewingKey,
  keys.spendingPubKey,
  keys.spendingKey
);
// matched: announcements that belong to you, with private keys
The recipient checks each announcement:
  1. Compute shared secret S = viewingKey * R (same ECDH, other side)
  2. Check view tag — if it doesn’t match, skip (rejects ~255/256 non-matches)
  3. Compute expected address = spendingPubKey + hash(S) * G
  4. If expected address matches announced address, it’s yours
Step 4: Recipient spends
import { deriveStealthPrivateKey } from "@wraith-protocol/sdk/chains/evm";

const privateKey = deriveStealthPrivateKey(
  keys.spendingKey,
  ephemeralPubKey,
  keys.viewingKey
);
// privateKey controls the stealth address
The private key: stealthPrivateKey = spendingKey + hash(sharedSecret) mod n

View Tags: Fast Filtering

Without view tags, scanning requires a full ECDH computation + point addition for every announcement. View tags add a 1-byte shortcut:
With 1000 announcements:
  Without view tags: 1000 full checks
  With view tags:    1000 byte comparisons + ~4 full checks
The view tag eliminates ~255/256 non-matching announcements with a single byte comparison.

EVM vs. Stellar

The same concept works on both chains, adapted to their cryptographic primitives:
StepEVM (secp256k1)Stellar (ed25519)
Key derivationkeccak256(r), keccak256(s) of wallet sigSHA-256("wraith:spending:" || sig)
ECDHsecp256k1.getSharedSecretX25519 (Montgomery form)
Hash to scalarkeccak256(S) mod nSHA-256("wraith:scalar:" || S) mod L
View tagkeccak256(S)[0]SHA-256("wraith:tag:" || S)[0]
Address format0x... (20 bytes)G... (56 chars)
Signingsecp256k1 ECDSAed25519 with raw scalar

Standards

EVM stealth addresses are based on:
  • ERC-5564 — Stealth Address Messenger (announcement format)
  • ERC-6538 — Stealth Meta-Address Registry (meta-address storage)
Wraith extends these with:
  • WraithNames — human-readable .wraith name to meta-address mapping
  • WraithSender — atomic send + announce in one transaction
  • WraithWithdrawer — gas-sponsored withdrawals via EIP-7702

Visual Flow

Stealth Payment Full Flow

Using the Managed Agent

With the Wraith agent, all of this is handled automatically:
import { Wraith, Chain } from "@wraith-protocol/sdk";

const wraith = new Wraith({ apiKey: "wraith_..." });

const agent = await wraith.createAgent({
  name: "alice",
  chain: Chain.Horizen,
  wallet: "0x...",
  signature: "0x...",
});

// Sending handles stealth address generation + announcement
await agent.chat("send 0.1 ETH to bob.wraith");

// Scanning handles announcement detection + key derivation
await agent.chat("scan for payments");

// Withdrawing handles private key derivation + transaction signing
await agent.chat("withdraw all to 0xMyWallet");