Skip to main content
Built on the shoulders of giants - Special thanks to Umbra Cash for pioneering stealth payment infrastructure.

What You’ll Build

The @shakesco/private SDK lets you implement truly private crypto transactions. No one except the sender and receiver can link the payment to the recipient’s known address.

How It Works

Deep dive into stealth address cryptography

EIP-5564 Standard

Read the official Ethereum proposal

Installation

npm i @shakesco/private
Import the SDK components:
const shakesco = require("@shakesco/private");
const { KeyPair, RandomNumber, StealthKeyRegistry, utils } = shakesco;
const { IsUsersFunds, generateKeyPair, prepareSend } = shakesco;
Security Note: This implementation assumes a single private key secures your wallet and that you’re signing the same message hash. Multi-sig or threshold signatures require a different approach.

Complete Integration Workflow

Step 1: Check for Existing Stealth Keys

Before sending private transactions, verify if the recipient has registered stealth keys in the Umbra registry:
const provider = new ethers.JsonRpcProvider(process.env.RPC_URL);
const registry = new StealthKeyRegistry(provider);
const recipientId = "0x...."; // User's address

const { spendingPublicKey, viewingPublicKey } =
  await registry.getStealthKeys(recipientId);

if (!spendingPublicKey) {
  console.log("User needs to register stealth keys first");
}
Spending Keys (spendingPublicKey)
  • Used to generate stealth addresses where funds are sent
  • Only the recipient can derive the private key to spend from these addresses
Viewing Keys (viewingPublicKey)
  • Allow scanning for incoming private transactions
  • Can detect payments without exposing spending ability
  • Safe to use for monitoring wallets
This separation means you can check for payments without risking your funds.

Step 2: Register Stealth Keys

If the user hasn’t registered, you’ll need to generate and register their key pairs.
For account abstraction wallets, register via a contract call:
const provider = new ethers.JsonRpcProvider(process.env.RPC_URL);
const signer = new ethers.Wallet(process.env.PRIV_KEY, provider);
const signature = await signer.signMessage(messageHash);

// Generate deterministic key pairs from signature
const { spendingKeyPair, viewingKeyPair } = await generateKeyPair(signature);

const registry = new StealthKeyRegistry(provider);
const { spendingPrefix, spendingPubKeyX, viewingPrefix, viewingPubKeyX } =
  await registry.setSmartStealthKeys(
    spendingKeyPair.publicKeyHex,
    viewingKeyPair.publicKeyHex
  );
Then execute the registration via your smart wallet:
const calldata = accountABI.encodeFunctionData("execute", [
  "0x31fe56609C65Cd0C510E7125f051D440424D38f3",
  0,
  stealthABI.encodeFunctionData("setStealthKeys", [
    spendingPrefix,
    spendingPubKeyX,
    viewingPrefix,
    viewingPubKeyX,
  ]),
]);
Storing the viewingKeyPair.privateKeyHex for users is acceptable - it only enables transaction scanning, not spending. This lets you build features like automatic payment detection.

Step 3: Generate Stealth Address for Payment

Ready to send a private transaction? Generate a one-time stealth address:
const payee = "0x..."; // Recipient's address
const provider = new ethers.JsonRpcProvider(process.env.RPC_URL);

const { stealthKeyPair, pubKeyXCoordinate, encrypted } = await prepareSend(
  payee,
  provider
);

console.log(stealthKeyPair.address); // ← Send funds HERE
console.log(pubKeyXCoordinate); // ← Share with recipient
console.log(encrypted.ciphertext); // ← Share with recipient
1

Send funds to the stealth address

Transfer crypto to stealthKeyPair.address - this is a brand new address only the recipient can control
2

Publish announcement data

The recipient needs pubKeyXCoordinate and encrypted.ciphertext to prove ownership and spend the funds

Step 4: Announce the Payment

Critical: Without the announcement data, the recipient cannot access their funds!
Emit this event from your private transaction contract:
event Announcement(
  address indexed receiver,    // Stealth address
  uint256 amount,
  address indexed tokenAddress,
  bytes32 pkx,                 // pubKeyXCoordinate
  bytes32 ciphertext           // encrypted.ciphertext
);
Recipients scan the blockchain for Announcement events. Use indexing services for efficient scanning:
  • The Graph - Decentralized indexing protocol
  • Moralis - Web3 data APIs
  • Custom indexer - Query RPC nodes directly (slower)
These services let recipients quickly find all announcements directed to their registered keys.

Step 5: Scan for Incoming Funds

Recipients check if an announcement belongs to them:
IsUsersFunds(
  object.announcements[i],
  provider,
  secret, // Viewing private key
  sender
).then((data) => {
  if (data.isForUser) {
    // 🎉 This payment is for you!
    console.log("Amount:", data.amount);
    console.log("Token:", data.tokenAddress);
    console.log("Stealth address:", data.stealthAddress);
  }
});

Step 6: Spend the Private Funds

Once you’ve confirmed funds belong to you, derive the private key to spend them:
const provider = new ethers.JsonRpcProvider(process.env.RPC_URL);
const signer = new ethers.Wallet(process.env.PRIV_KEY, provider);
const signature = await signer.signMessage(messageHash);

// Regenerate your key pairs (deterministic from signature)
const { spendingKeyPair, viewingKeyPair } = await generateKeyPair(signature);

// Decrypt the random number used to generate the stealth address
const payload = {
  ephemeralPublicKey: uncompressedPubKey,
  ciphertext: ciphertext,
};

const random = await viewingKeyPair.decrypt(payload);

// Compute the stealth address private key
const stealthPrivateKey = KeyPair.computeStealthPrivateKey(
  spendingKeyPair.privateKeyHex,
  random
);

// Now spend the funds!
const wallet = new ethers.Wallet(stealthPrivateKey, provider);
const txResponse = await wallet.sendTransaction({
  value: ethers.parseEther(value),
  to: destinationAddress,
});

await txResponse.wait();
console.log("✅ Private funds successfully transferred!");

What’s Next?

While stealth addresses provide strong privacy today, zero-knowledge proofs will eventually offer even better solutions. Until then, stealth payments are the best way to bring privacy to Ethereum transactions.

Additional Resources

Umbra Protocol Docs

Learn from the pioneers of stealth payments

EIP-5564 Discussion

Join the Ethereum community conversation

GitHub Repository

View source code and contribute