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.

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 { 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", [
  CONTRACTS[chainID]["StealthRegistry"],
  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