Skip to content
Stealth Pay

Stealth Pay

Stealth-address payment system using Bitgo wallet

Created on 15th March 2026

•

Stealth Pay

Stealth Pay

Stealth-address payment system using Bitgo wallet

The problem Stealth Pay solves

Bitcoin's Privacy Gap

Bitcoin is often described as anonymous — it isn't. Every transaction is permanently recorded on a public ledger. If someone knows your wallet address, they can see your entire payment history: every incoming transaction, every balance, every counterparty. Share your address once to receive a payment and you've permanently exposed your financial activity to anyone who looks.

Real-World Implications

This matters in real situations:

  • A freelancer sharing their Bitcoin address for payment reveals their total earnings and every other client who ever paid them.
  • A business accepting Bitcoin donations publicly exposes its revenue stream.
  • Anyone paying a known address can be linked to the recipient by a blockchain analytics firm.

What This Project Does

Stealth-Pay gives every recipient a stealth meta-address — a pair of public keys they can share openly. Senders use those keys to compute a unique, one-time Bitcoin address per payment using ECDH cryptography. Each payment lands at a different address that is:

  • Unlinkable — no two payments to the same recipient share an address.
  • Unscannable — nobody can find your payments without your private view key, not even by scanning the blockchain exhaustively.
  • Trustless — the spend key that authorizes withdrawals never leaves your browser, encrypted with a passphrase only you know.

Who Can Use It

Use caseHow it helps
Freelancers & contractorsPublish one stealth meta-address; each client pays a different on-chain address
Crypto donationsAccept donations without exposing total funds raised or donor list
Business payrollPay employees in Bitcoin without revealing who gets paid or how much
Personal transfersReceive funds from friends or exchanges without linking addresses to your identity
Privacy-sensitive paymentsAny scenario where the receiver's address should not be publicly correlatable

Why It's Safer Than Alternatives

Most "privacy" approaches on Bitcoin today are either mixing (which relies on trusted coordinators or is trivially deanonymized) or address reuse avoidance (which still doesn't help if the recipient shares the same address twice). Stealth addresses solve this at the protocol level — the sender derives a fresh address mathematically, no coordinator required, no cooperation needed beyond sharing the meta-address once.

The custodial risk is also eliminated by design: the platform cannot move your funds. The spend private key is encrypted in your browser and the server stores null — following the same model as MetaMask's local keystore.

Challenges we ran into

1. Building a Non-Custodial System Without Compromising UX

The biggest design challenge was ensuring the spend private key — the key that authorises withdrawals from stealth addresses — never touches our server, while still keeping the product usable.

We took direct inspiration from MetaMask's model: the spend key is returned to the client exactly once on generation, encrypted in the browser using WebCrypto AES-GCM + PBKDF2 (200,000 iterations), and stored in localStorage. The server stores null for the spend key field — by design. The view key (used only for scanning, not spending) lives server-side so scanning can run without user interaction. This split meant we had to build two separate signing paths: BitGo's MPC TSS for outbound sends, and a fully client-side PSBT pipeline for sweeps.

2. Client-Side Bitcoin Signing in the Browser

Signing a Bitcoin PSBT (bitcoinjs-lib v7) in the browser using @noble/secp256k1 v2 surfaced several non-obvious incompatibilities:

  • initEccLib() requirement — bitcoinjs-lib v7 ships with no ECC backend. Calling signAllInputs without first registering an ECC library throws silently per input, surfacing only as the opaque "No inputs were signed" error. Fixed by writing a custom TinySecp256k1Interface adapter backed by @noble/secp256k1.
  • hmacSha256Sync not wired — @noble/secp256k1 v2 ships with hmacSha256Sync = undefined. Its synchronous sign() path (required by the PSBT signer interface) calls HMAC-DRBG internally, and throws if this is not set. This error was also swallowed silently by signAllInputs, giving the same misleading message. Fixed by wiring @noble/hashes HMAC into secp.etc.hmacSha256Sync at module load.
  • Buffer polyfill — bitcoinjs-lib uses Node.js Buffer throughout. In the browser bundle it wasn't resolving. Required adding the buffer npm package and configuring a webpack fallback in next.config.mjs.

3. Implementing the Stealth Address Protocol Correctly

There is no ready-made Bitcoin stealth address library. We implemented the full dual-key scheme from scratch in a dedicated @stealth/crypto monorepo package:

  • Sender side: generate ephemeral (r, R), derive P = H(r·A)·G + B, convert to P2WPKH
  • Receiver side: derive S = H(a·R), reconstruct P = S·G + B, compare against stored one_time_address
  • Spending: derive private key x = S + b mod n, build and sign PSBT client-side

Getting the scalar arithmetic (addScalars, scalarMult, pointAdd) correct on the secp256k1 curve — and ensuring the derived address on the receiver side matched what the sender computed — required careful implementation and testing at each step.

4. Blockstream Testnet Reliability

Bitcoin testnet infrastructure is notoriously unreliable. During development, Blockstream's testnet API repeatedly returned 504 Gateway Time-out for both UTXO fetches and transaction broadcasts, stalling the entire sweep flow. We resolved this by adding mempool.space/testnet as a sequential fallback with explicit AbortSignal timeouts (15s for UTXO fetches, 20s for broadcasts) across all three places that contact the Bitcoin network.

Tracks Applied (1)

Best Privacy Application using BitGo

Stealth-Address Payment System on Bitcoin Testnet The bounty explicitly named a stealth-address payment system as its p...Read More
BitGo

BitGo

Cheer Project

Cheering for a project means supporting a project you like with as little as 0.0025 ETH. Right now, you can Cheer using ETH on Arbitrum, Optimism and Base.

Discussion

Builders also viewed

See more projects on Devfolio