Website: https://www.noktopay.com
EIP-5564 Stealth Addresses for Solana - Privacy-preserving transactions using unlinkable one-time addresses.
This project implements EIP-5564 stealth address protocol adapted for Solana. It enables senders to pay recipients without creating a public on-chain link between them.
- Recipient generates a stealth meta-address (spending + viewing keypairs) and publishes it
- Sender uses the meta-address to derive a unique one-time stealth address for each payment
- Sender transfers funds to the stealth address and emits an announcement
- Recipient scans announcements using their viewing key to detect payments
- Recipient derives the private key to spend from their stealth address
Only the recipient can detect and spend from stealth addresses - observers see no connection between payments.
sol-eip5564/
├── programs/sol-eip5564/ # Anchor program (Rust)
│ └── src/lib.rs # On-chain program
├── sdk/ # TypeScript SDK
│ ├── src/ # Source code
│ │ ├── index.ts # Public exports
│ │ ├── keys.ts # Key generation
│ │ ├── stealth.ts # Stealth address derivation
│ │ ├── scanning.ts # Announcement scanning
│ │ └── utils.ts # Utilities (hashing, encoding)
│ └── dist/ # Compiled output
├── tests/ # Integration tests
└── scripts/ # Demo scripts
The Anchor program provides four instructions:
| Instruction | Description |
|---|---|
register_stealth_meta_address |
Store spending/viewing pubkeys on-chain |
announce |
Emit stealth announcement event |
stealth_transfer_sol |
Transfer SOL + emit announcement atomically |
stealth_transfer_token |
Transfer SPL tokens + create ATA + emit announcement |
Program ID: 463ouCTiiJqyitwkcfwhtuS1vcDrPTEibiYKTQeVu62a (Mainnet & Devnet)
The SDK handles all cryptographic operations client-side:
- Key Generation: secp256k1 keypairs for spending/viewing
- Stealth Address Derivation: ECDH + Ed25519 for spendable Solana addresses
- Scanning: View tag filtering (99.6% early rejection) + full verification
- Spending: Derive Ed25519 keypair from stealth secret
- SPL Token Support: Token metadata encoding/decoding, ATA derivation
See sdk/README.md for full API documentation.
# Clone and install
git clone https://github.com/BOBER3r/nokto-protocol.git
cd nokto-protocol
yarn install
# Build program
anchor build
# Build SDK
cd sdk && npm run build# Start local validator
solana-test-validator
# Run tests (in another terminal)
anchor test --skip-local-validatorimport {
generateStealthMetaAddress,
parseStealthMetaAddress,
generateStealthAddress,
deriveStealthKeypair,
checkStealthAddress
} from 'sol-stealth-sdk';
// === RECIPIENT: Generate meta-address (once) ===
const metaAddress = generateStealthMetaAddress();
console.log(metaAddress.encoded); // Share this publicly
// Keep spending.privateKey and viewing.privateKey SECRET
// === SENDER: Send to stealth address ===
const recipientMeta = parseStealthMetaAddress("st:sol:1:02abc...:03xyz...");
const stealth = generateStealthAddress(recipientMeta);
// Transfer SOL to stealth.stealthAddress
// Store announcement: { ephemeralPubkey, viewTag, stealthAddress }
// === RECIPIENT: Scan and spend ===
const result = checkStealthAddress(
announcement,
myViewingPrivateKey,
mySpendingPublicKey
);
if (result.isMine) {
const keypair = deriveStealthKeypair(
announcement.ephemeralPubkey,
mySpendingPrivateKey,
myViewingPrivateKey
);
// Sign transactions with keypair
}Recipient Setup:
k_spend, K_spend = secp256k1 keypair (spending)
k_view, K_view = secp256k1 keypair (viewing)
meta-address = encode(K_spend, K_view)
Sender Derivation:
r, R = ephemeral secp256k1 keypair
S = r * K_view (ECDH shared secret)
h = keccak256(S) (hash)
viewTag = h[0] (first byte)
P_combined = K_spend + h*G (secp256k1 point)
seed = sha256(P_combined) (Ed25519 seed)
stealthAddress = Ed25519(seed).publicKey
Recipient Recovery:
S = k_view * R (ECDH shared secret)
h = keccak256(S)
P_combined = (k_spend + h) * G (reconstruct point)
seed = sha256(P_combined)
keypair = Ed25519(seed) (can sign transactions)
The view tag (first byte of hash) allows recipients to reject 255/256 (99.6%) of non-matching announcements with a single byte comparison, before expensive point operations.
| Key | Who Holds | Capability |
|---|---|---|
| Spending Private | Recipient only | Spend from stealth addresses |
| Viewing Private | Recipient (or delegate) | Detect incoming payments |
| Spending Public | Anyone (in meta-address) | Derive stealth addresses |
| Viewing Public | Anyone (in meta-address) | Derive stealth addresses |
Key separation allows:
- Delegating scanning to a service (share viewing key)
- Service can detect payments but CANNOT spend
- Only spending key holder can move funds
- Rust 1.70+
- Solana CLI 1.17+
- Anchor 0.29+
- Node.js 18+
# Program
anchor build
# SDK
cd sdk && npm run build# Full test suite
anchor test
# With local validator already running
anchor test --skip-local-validator# Devnet
anchor deploy --provider.cluster devnet
# Mainnet (be careful!)
anchor deploy --provider.cluster mainnet- Off-chain scanning: Recipients must scan announcements off-chain. An indexer service would improve UX.
- No registry UI: Meta-addresses stored on-chain but no explorer integration yet.
- Token-2022: Currently uses standard SPL Token. Token-2022 support via token_interface.
- EIP-5564: Stealth Addresses
- EIP-5564 Reference Implementation
- Vitalik's Incomplete Guide to Stealth Addresses
MIT License - see LICENSE