Skip to main content

What are Stealth Addresses?

A stealth address lets you receive funds or contract calls without linking transactions to your identity. Every sender generates a unique one-time address just for you — nobody watching the chain can tell which transactions belong to the same wallet. NEXUS implements the ERC-5564 Dual-Key Stealth Address Protocol using secp256k1 ECDH.
Stealth addresses are live in V1. They are separate from Zcash shielded pools (z-addr), which are planned for V2.

How it Works (Plain English)

  1. You publish a stealth meta-address — two public keys (scan key + spend key)
  2. Sender looks up your meta-address, generates a random one-time key, and computes a unique address just for this transaction
  3. Transaction goes to that one-time address — no link to your real identity on-chain
  4. You scan new transactions using your private scan key to find yours
  5. You spend using a derived private key only you can compute
You publish:  scan_pubkey + spend_pubkey  (66 bytes, your stealth meta-address)

Sender:
  1. Generate random ephemeral key R
  2. ECDH: shared_secret = HKDF(R × scan_pubkey)
  3. one_time_address = spend_pubkey + shared_secret × G
  4. Attach (R, view_tag) to transaction

You (scanning):
  1. For each tx: shared_secret = HKDF(scan_privkey × R)
  2. View tag matches? → Compute full address to confirm
  3. Match → Spend with: spend_privkey + shared_secret_scalar

View Tags: Fast Scanning

Every transaction carries a 1-byte view tag (the first byte of the HKDF output). Before doing any expensive elliptic curve math, the scanner checks the view tag — this filters out 99.6% of non-matching transactions instantly.

Privacy Properties

PropertyStatus
Sender cannot be linked to recipient
Multiple receipts unlinkable
Works for transfers
Works for contract calls
Scan key can be delegated (watch-only)
Spend key never revealed during scan

Stealth Contract Calls

Stealth addresses work for smart contract interactions too, not just transfers. The NEXUS miner resolves the stealth address to your real address before execution, so contracts see your real msg.sender — approvals, ownership checks, and balances all work correctly. The public block record only shows the unlinkable one-time address.

Using Stealth Addresses (TypeScript SDK)

import {
  deriveStealthKeyPair,
  encodeStealthMetaAddress,
  generateStealthAddress,
  getMyStealthTransactions,
  deriveStealthPrivkey,
} from '@yattacorp/nexus-sdk';

// ── Recipient: set up your stealth keypair ──────────────────────────────────
const stealthKeys = deriveStealthKeyPair(myMasterPrivkey);

// Publish this — it's your stealth meta-address (safe to share)
const metaAddress = encodeStealthMetaAddress(stealthKeys);
// e.g. "02abc...def02ghi...jkl" (66 bytes hex)

// ── Sender: generate a one-time address for the recipient ───────────────────
const { stealthAddress, ephemeralPubkey, viewTag } = generateStealthAddress(
  recipientScanPubkeyHex,
  recipientSpendPubkeyHex,
);
// Use stealthAddress as tx.from, attach ephemeralPubkey + viewTag to tx

// ── Recipient: scan for incoming transactions ───────────────────────────────
const myTxs = getMyStealthTransactions(
  allStealthTxsFromSnapshot,  // fetched from node
  stealthKeys.scanPrivkey,
  stealthKeys.spendPubkey,
);

// ── Recipient: derive private key to spend ──────────────────────────────────
for (const tx of myTxs) {
  const privkey = deriveStealthPrivkey(stealthKeys.spendPrivkey, tx.sharedSecretScalar);
  // sign transactions with privkey
}

Key Derivation

Stealth scan and spend keys are derived from your master key using HKDF-SHA256 with domain separation:
scan_privkey  = HKDF(master, salt="nexus-stealth-scan",  info="scan-key-v2",  32 bytes)
spend_privkey = HKDF(master, salt="nexus-stealth-spend", info="spend-key-v2", 32 bytes)
This means a single master key controls your entire identity, while scan and spend keys are separate — you can share your scan key with a watchtower service without giving it spending ability.

Scanning Privacy

Scanning is fully client-side — your scan private key never leaves your device. You fetch the global stealth transaction snapshot from a NEXUS node, then compute locally which transactions are yours. The node never learns which transactions belong to you.

Cryptographic Details

ComponentSpecification
Key agreementECDH on secp256k1
Key derivationHKDF-SHA256 (RFC 5869)
Domainnexus-stealth-address-v2
View tag1 byte (first HKDF output byte)
Address format32-byte x-only pubkey (Taproot-compatible)
ComparisonConstant-time to prevent timing attacks