sol-stealth-sdk
v0.2.0
Published
EIP-5564 Stealth Addresses SDK for Solana - Generate, send to, and spend from unlinkable stealth addresses
Maintainers
Readme
sol-stealth-sdk
EIP-5564 Stealth Addresses SDK for Solana. Generate, send to, and spend from unlinkable stealth addresses.
What are Stealth Addresses?
Stealth addresses allow a sender to pay a recipient without creating a public link between them on-chain. The recipient publishes a single "meta-address", and senders derive unique one-time addresses for each payment. Only the recipient can detect and spend from these addresses.
Installation
npm install sol-stealth-sdk @solana/web3.jsQuick Start
Recipient: Generate Meta-Address
import { generateStealthMetaAddress } from 'sol-stealth-sdk';
// Generate a new stealth meta-address (do this once)
const metaAddress = generateStealthMetaAddress();
// Share this publicly - senders use it to pay you
console.log(metaAddress.encoded);
// st:sol:1:02abc...def:03xyz...
// KEEP THESE SECRET - needed to detect and spend payments
console.log(metaAddress.spending.privateKey); // 32 bytes
console.log(metaAddress.viewing.privateKey); // 32 bytesSender: Send to Stealth Address
import { parseStealthMetaAddress, generateStealthAddress } from 'sol-stealth-sdk';
import { Connection, Transaction, SystemProgram, sendAndConfirmTransaction } from '@solana/web3.js';
// Parse recipient's meta-address
const recipientMeta = parseStealthMetaAddress("st:sol:1:02abc...def:03xyz...");
// Generate a unique stealth address for this payment
const stealth = generateStealthAddress(recipientMeta);
// Send SOL to the stealth address
const tx = new Transaction().add(
SystemProgram.transfer({
fromPubkey: senderWallet.publicKey,
toPubkey: stealth.stealthAddress,
lamports: amount,
})
);
await sendAndConfirmTransaction(connection, tx, [senderWallet]);
// Store/emit announcement data so recipient can find the payment
// (ephemeralPubkey, viewTag, stealthAddress)Recipient: Scan and Spend
import {
checkStealthAddress,
deriveStealthKeypair,
StealthAnnouncement
} from 'sol-stealth-sdk';
// Check if an announcement is for you
const result = checkStealthAddress(
announcement,
myViewingPrivateKey,
mySpendingPublicKey,
mySpendingPrivateKey // optional, needed to derive spending key
);
if (result.isMine) {
// Derive the keypair to spend from this stealth address
const stealthKeypair = deriveStealthKeypair(
announcement.ephemeralPubkey,
mySpendingPrivateKey,
myViewingPrivateKey
);
// Now you can sign transactions from stealthKeypair.publicKey
const withdrawTx = new Transaction().add(
SystemProgram.transfer({
fromPubkey: stealthKeypair.publicKey,
toPubkey: myMainWallet,
lamports: balance,
})
);
withdrawTx.sign(stealthKeypair);
await connection.sendRawTransaction(withdrawTx.serialize());
}API Reference
Key Generation
generateStealthMetaAddress()
Generate a new random stealth meta-address with spending and viewing keypairs.
const meta = generateStealthMetaAddress();
// Returns: {
// spending: { privateKey, publicKey },
// viewing: { privateKey, publicKey },
// encoded: "st:sol:1:..."
// }deriveStealthMetaAddress(walletSecretKey)
Derive a deterministic stealth meta-address from a Solana wallet's secret key. Same wallet always produces the same meta-address.
import { Keypair } from '@solana/web3.js';
const wallet = Keypair.generate(); // or load from file
const meta = deriveStealthMetaAddress(wallet.secretKey);
// Recover later with same wallet:
const metaRecovered = deriveStealthMetaAddress(wallet.secretKey);
// metaRecovered.encoded === meta.encoded ✓deriveStealthMetaAddressFromMnemonic(mnemonic, passphrase?)
Derive a deterministic stealth meta-address from a BIP39 mnemonic phrase.
Follows EIP-5564 style derivation path: m/5564'/501'/0'
const meta = deriveStealthMetaAddressFromMnemonic(
"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"
);
// With optional passphrase (different keys):
const metaWithPass = deriveStealthMetaAddressFromMnemonic(
"abandon abandon ...",
"my-secret-passphrase"
);deriveStealthMetaAddressFromSeed(seed)
Derive from a raw 32-byte seed. Useful for custom key derivation schemes.
const seed = new Uint8Array(32).fill(42);
const meta = deriveStealthMetaAddressFromSeed(seed);parseStealthMetaAddress(encoded: string)
Parse an encoded meta-address string into public keys.
const parsed = parseStealthMetaAddress("st:sol:1:02abc...:03xyz...");
// Returns: { spendingPubkey, viewingPubkey, schemeId }Key Derivation Comparison
| Method | Deterministic | Backup | Security |
|--------|--------------|--------|----------|
| generateStealthMetaAddress() | ❌ Random | Must store keys separately | Keys isolated from wallet |
| deriveStealthMetaAddress() | ✅ From wallet | Wallet backup covers stealth | Wallet compromise = stealth compromise |
| deriveStealthMetaAddressFromMnemonic() | ✅ From mnemonic | Seed phrase covers stealth | Seed compromise = stealth compromise |
Stealth Address Operations
generateStealthAddress(metaAddress)
Generate a one-time stealth address for a recipient.
const result = generateStealthAddress(recipientMeta);
// Returns: {
// stealthAddress: PublicKey, // Send funds here
// ephemeralPubkey: Uint8Array, // Include in announcement
// viewTag: number, // Include in announcement
// schemeId: number,
// stealthSeed: Uint8Array
// }deriveStealthKeypair(ephemeralPubkey, spendingPrivate, viewingPrivate)
Derive the Solana keypair to spend from a stealth address.
const keypair = deriveStealthKeypair(
announcement.ephemeralPubkey,
mySpendingPrivateKey,
myViewingPrivateKey
);
// Returns: Keypair (can sign transactions)Scanning
checkStealthAddress(announcement, viewingPrivate, spendingPubkey, spendingPrivate?)
Check if an announcement belongs to you.
const result = checkStealthAddress(announcement, viewingKey, spendingPubkey);
// Returns: { isMine: boolean, stealthAddress?, stealthPrivateKey? }scanAnnouncements(announcements[], viewingPrivate, spendingPubkey, spendingPrivate?)
Scan multiple announcements and return matches.
const matches = scanAnnouncements(announcements, viewingKey, spendingPubkey, spendingKey);
// Returns: AnnouncementCheckResult[]SPL Token Support
isTokenTransfer(metadata: Uint8Array)
Check if announcement metadata contains token transfer info.
if (isTokenTransfer(announcement.metadata)) {
// This is a token transfer, not SOL
}decodeTokenMetadata(metadata: Uint8Array)
Decode token information from announcement metadata.
const tokenInfo = decodeTokenMetadata(announcement.metadata);
// Returns: { mint: PublicKey, amount: bigint, decimals: number, extraMetadata: Uint8Array }
console.log(`Received ${formatTokenAmount(tokenInfo.amount, tokenInfo.decimals)} tokens`);
console.log(`Token mint: ${tokenInfo.mint.toString()}`);getStealthATA(stealthAddress, mint, tokenProgramId?)
Get the Associated Token Account address for a stealth address.
const stealthAta = getStealthATA(stealthAddress, tokenMint);
// Use this to check token balance or create transfer instructionsformatTokenAmount(amount: bigint, decimals: number)
Format raw token amount for display.
formatTokenAmount(1500000n, 6); // Returns: "1.5"parseTokenAmount(amountStr: string, decimals: number)
Parse display amount to raw units.
parseTokenAmount("1.5", 6); // Returns: 1500000nSecurity Considerations
- Private Keys: Never share or expose your spending/viewing private keys
- Viewing Key: Can be shared with a trusted service for scanning (they can detect payments but NOT spend)
- Spending Key: Required to spend - keep this maximally secure
- Ephemeral Keys: Generated fresh for each payment, don't reuse
How It Works
- Recipient generates secp256k1 keypairs for spending (K_spend) and viewing (K_view)
- Sender generates ephemeral keypair (r, R = r*G)
- Sender computes shared secret via ECDH: S = r * K_view
- Sender derives: hash = keccak256(S), viewTag = hash[0]
- Sender computes combined point: P = K_spend + hash*G
- Sender derives Ed25519 seed: seed = sha256(P)
- Stealth address = Ed25519 keypair from seed
Recipient reverses steps 3-7 using their private keys to derive the same keypair.
On-Chain Program
This SDK is designed to work with the sol-eip5564 Anchor program for on-chain meta-address registration and transfer announcements.
Devnet Program ID: 463ouCTiiJqyitwkcfwhtuS1vcDrPTEibiYKTQeVu62a
License
MIT
