Skip to main content

Protocol Flow

From payment to spendable funds​

The protocol has six stages. Each one exists to separate public discovery from private ownership while keeping the result usable in today's wallets.

SPECTER protocol flow


Stage 1: Generate your keys​

SPECTER key generation

The backend generates a hybrid identity — two keypairs of different types:

  • Spending keypair (spending_pub / spending_sk) - a secp256k1 keypair that determines where money lands and, later, spends it
  • Viewing keypair (viewing_pk / viewing_sk) - an ML-KEM-768 keypair that lets you find your payments

These are bundled into your meta-address: the public-facing profile that senders use.

curl -s -X POST https://backend.specterpq.com/api/v1/keys/generate | jq .

Response includes meta_address, spending_pub, spending_sk, viewing_pk, viewing_sk, protocol_version.

Setup flow: the recipient generates a spending keypair and a viewing keypair, then publishes a single meta-address

Setup: key generation produces a secp256k1 spending keypair and an ML-KEM-768 viewing keypair, bundled into one meta-address

Stage 2: Publish your meta-address​

Make your meta-address discoverable. Two options:

ENS (Ethereum): Upload meta-address to IPFS, set your ENS text record to point at the IPFS CID. Senders resolve alice.eth to your meta-address automatically.

SuiNS (Sui): Same pattern with Sui name records.

Or just share the raw meta-address string directly. Name services are convenient, not required.


Stage 3: Sender creates the payment​

This is where ML-KEM does its work. The sender:

  1. Reads your viewing_pk from the meta-address
  2. Runs ML-KEM encapsulation to produce:
    • A ciphertext (1,088 bytes) - this is the encrypted hint
    • A shared secret (32 bytes) - known to both sender and recipient
  3. Computes a 1-byte view tag from the shared secret
  4. Derives a stealth address (Ethereum and/or Sui) from the shared secret + your spending_pub
curl -s -X POST https://backend.specterpq.com/api/v1/stealth/create \
-H "Content-Type: application/json" \
-d '{"meta_address":"<RECIPIENT_META_ADDRESS>"}' | jq .

The sender then sends ETH/tokens to the returned stealth_address.

Send flow: the sender encapsulates to the viewing key, derives a one-time stealth address, and publishes an announcement with a view tag

Send: ML-KEM-768 encapsulation creates the shared secret, a one-time stealth address, and a view tag

Stage 4: Sender posts an announcement​

The announcement is the public breadcrumb. Payments settle on Ethereum, Arbitrum, Monad, or Sui, but every announcement is recorded on Monad Testnet, which serves as the registry chain. It contains:

FieldPurpose
payment_idServer-held pending payment binding from /stealth/create
tx_hashMonad announce transaction hash. Required when the relayer is not configured
payment_tx_hashSource-chain payment transaction hash (optional)
source_chain_idEIP-155 source-chain ID (optional)
amountPayment amount (optional)
chainSource chain name (optional)
tokenERC-20 token contract for payment verification (optional)
curl -s -X POST https://backend.specterpq.com/api/v1/registry/announcements \
-H "Content-Type: application/json" \
-d '{
"payment_id":"<PAYMENT_ID_FROM_STEALTH_CREATE>",
"tx_hash":"0x...",
"amount":"0.1",
"chain":"ethereum"
}' | jq .

Preferred publishes use the server-held shared secret to encrypt payment metadata into a 93-byte blob. The fallback announcement path emits 77-byte plaintext metadata because the server has no shared secret.


Stage 5: Recipient scans​

This is the discovery process. The recipient:

  1. Loads announcements from the registry
  2. For each announcement, decapsulates the ciphertext using viewing_sk
  3. Computes the expected view tag from the shared secret
  4. Fast path: if the view tag doesn't match, skip (filters ~99.6% of announcements)
  5. Full path: if it matches, derive the stealth address and compare
curl -s -X POST https://backend.specterpq.com/api/v1/stealth/scan \
-H "Content-Type: application/json" \
-d '{
"viewing_sk":"<HEX>",
"spending_pub":"<HEX>"
}' | jq .

Recipient scan flow: filter announcements by view tag, decapsulate the matches, and recover the stealth private key

Scanning: view tags filter roughly 99.6% of announcements, then decapsulation recovers the stealth key

Stage 6: Spend the funds​

The scan returns everything needed to locate and unlock the funds:

FieldWhat it is
stealth_addressThe Ethereum address holding the funds
stealth_sui_addressThe Sui address (if applicable)
shared_secretThe per-payment ML-KEM secret used to derive the spend key

The scan is spend-key-free — it never receives or returns a private key. To spend, combine shared_secret with your spending secret key locally: the one-time private key is spending_sk + tweak(shared_secret) mod n. Import the derived key into any Ethereum wallet and spend normally.

Warning

viewing_sk and every shared_secret in the scan response are sensitive — a shared secret plus your spending secret key recovers a spendable key. Treat them like a seed phrase. Never log them, never expose them in analytics.


The three objects that matter​

ObjectWhat it doesWho holds it
Meta-addressPublic receiving profilePublished by recipient, read by senders
AnnouncementEncrypted breadcrumb for discoveryPublished by sender, scanned by recipient
Stealth private keyControls the fundsOnly the recipient

What sender and recipient each see​

The sender needs only the recipient's meta-address or name.

They never learn the recipient's real wallet address. They derive a fresh stealth address, send funds to it, and post an announcement. From their side, it's three API calls.


The security split​

The receiving path (stages 1-5) uses ML-KEM-768 and is post-quantum safe.

The spending path (stage 6) produces a secp256k1 key for Ethereum wallet compatibility. That part is classical.

Read Security Boundaries for why this split exists and how it could be resolved.

Post-quantum crypto details

What ML-KEM-768 actually does under the hood.

Try the full flow

Run every stage against the live API.