This document outlines security considerations, identified issues, and recommended improvements for the StealthSol stealth address protocol implementation.
- Current Security Status
- Critical Issues
- High Priority Improvements
- Medium Priority Improvements
- Low Priority Improvements
- Security Best Practices
| Asset | Protection Level | Notes |
|---|---|---|
| Recipient Identity | ✅ Strong | Each payment goes to unique address |
| Payment Linkability | ✅ Strong | Addresses are unlinkable without scan key |
| Spending Keys | Derived correctly, but storage needs work | |
| Secret Key Storage | ❌ Weak | Currently stored in plaintext JSON |
- Sender identity (visible on-chain)
- Transaction amounts (visible on-chain)
- Transaction timing (visible on-chain)
Location: cli/src/config.rs:53-74
Issue: Secret keys are stored as plaintext hex strings in ~/.stealth/keys.json.
// Current implementation
pub struct StoredKeys {
pub scan_secret: String, // Plaintext hex!
pub spend_secret: String, // Plaintext hex!
// ...
}Risk: Any process with file read access can steal the keys.
Recommended Fix:
use aes_gcm::{Aes256Gcm, Key, Nonce};
use argon2::{Argon2, password_hash::SaltString};
pub struct EncryptedKeys {
/// Encrypted key blob (AES-256-GCM)
pub ciphertext: Vec<u8>,
/// Nonce for AES-GCM
pub nonce: [u8; 12],
/// Salt for key derivation
pub salt: [u8; 32],
/// Argon2 parameters
pub argon2_params: Argon2Params,
}
impl EncryptedKeys {
pub fn encrypt(keys: &StoredKeys, password: &str) -> Result<Self> {
// 1. Derive encryption key from password using Argon2id
// 2. Encrypt with AES-256-GCM
// 3. Store ciphertext + nonce + salt
}
pub fn decrypt(&self, password: &str) -> Result<StoredKeys> {
// 1. Derive key from password
// 2. Decrypt and verify authentication tag
}
}Dependencies to add:
aes-gcm = "0.10"
argon2 = "0.5"Location: programs/stealth/src/crypto/keys.rs:12-31
Issue: The validate_curve_point function only checks for all-zeros and all-ones, not actual curve membership.
// Current - too permissive
pub fn validate_curve_point(bytes: &[u8; 32]) -> bool {
if bytes.iter().all(|&b| b == 0) { return false; }
if bytes.iter().all(|&b| b == 0xFF) { return false; }
true // Accepts many invalid points!
}Risk: Attackers could submit invalid curve points that cause issues during DKSAP operations.
Recommended Fix:
use curve25519_dalek::edwards::CompressedEdwardsY;
pub fn validate_curve_point(bytes: &[u8; 32]) -> bool {
// Reject identity and obviously invalid
if bytes.iter().all(|&b| b == 0) { return false; }
if bytes.iter().all(|&b| b == 0xFF) { return false; }
// Actually try to decompress the point
// Note: This is expensive on-chain, consider doing off-chain
CompressedEdwardsY::from_slice(bytes)
.decompress()
.is_some()
}Trade-off: Full validation is expensive on-chain (~10k compute units). Consider:
- Doing full validation off-chain in CLI
- Using a lighter on-chain check
- Accepting the risk for invalid payments (they just won't be spendable)
Location: programs/stealth/src/instructions/send.rs
Issue: Anyone can spam the announcement system, making scanning expensive.
Risk: DoS attack on recipients by creating millions of fake announcements.
Recommended Fix:
// Option 1: Require minimum payment amount
const MIN_PAYMENT_LAMPORTS: u64 = 10_000; // 0.00001 SOL
require!(amount >= MIN_PAYMENT_LAMPORTS, StealthError::PaymentTooSmall);
// Option 2: Charge for announcement creation
const ANNOUNCEMENT_FEE: u64 = 5_000;
// Transfer fee to protocol treasury
// Option 3: Use account compression (future enhancement)Issue: Secrets may be swapped to disk by the OS.
// Add to Cargo.toml
memsec = "0.7"
// In crypto.rs
use memsec::mlock;
impl StealthKeys {
pub fn generate() -> Self {
let mut keys = Self::generate_inner();
// Lock memory pages containing secrets
unsafe {
mlock(
&keys.scan_secret as *const _ as *const u8,
std::mem::size_of::<Scalar>()
);
mlock(
&keys.spend_secret as *const _ as *const u8,
std::mem::size_of::<Scalar>()
);
}
keys
}
}Location: cli/src/crypto.rs:205
Issue: Payment address comparison may leak timing information.
// Current - potentially timing-vulnerable
if expected_bytes == *payment_address {
// Recommended - constant time
use subtle::ConstantTimeEq;
if expected_bytes.ct_eq(payment_address).into() {Dependencies:
subtle = "2.5"Issue: View keys can be exported but there's no revocation mechanism.
Recommended:
- Add optional view key expiration
- Implement view key derivation with salt (allows multiple view keys)
- Add on-chain view key registry for revocation
pub struct DelegatedViewKey {
pub scan_pubkey: [u8; 32],
pub delegation_id: [u8; 16], // Unique ID
pub expires_at: Option<i64>, // Unix timestamp
pub permissions: ViewKeyPermissions,
}
pub struct ViewKeyPermissions {
pub can_see_amounts: bool,
pub can_see_sender: bool,
pub max_lookback_slots: Option<u64>,
}Location: cli/src/crypto.rs
Issue: Some crypto operations use expect() which panics.
// Current
let keypair = solana_sdk::signature::Keypair::from_bytes(&keypair_bytes)
.expect("Valid keypair bytes");
// Recommended
let keypair = solana_sdk::signature::Keypair::from_bytes(&keypair_bytes)
.map_err(|e| CryptoError::InvalidKeypair(e.to_string()))?;Issue: Scanning requires fetching all program accounts, which is O(n) and expensive.
Recommended:
// Add index account per recipient
#[account]
pub struct RecipientIndex {
pub recipient_registry: Pubkey,
pub announcement_count: u64,
pub announcements: Vec<Pubkey>, // Or use linked list
}
// Or use account compression with SPL Account CompressionLocation: cli/src/crypto.rs:79-83
Current: Uses rand::thread_rng() which is cryptographically secure but worth auditing.
Recommended:
use rand::rngs::OsRng;
fn random_scalar() -> Scalar {
let mut bytes = [0u8; 32];
OsRng.fill_bytes(&mut bytes); // Uses OS entropy
Scalar::from_bytes_mod_order(bytes)
}Issue: Users may not understand what IS and ISN'T private.
Recommended: Add clear warnings in CLI:
Warning: The following information is PUBLIC:
- Your wallet address (sender)
- The transaction amount
- The transaction time
Private information:
- The recipient's identity
- Connection between this payment and the recipient's other payments
Add checksum to meta-address format to detect typos:
pub fn format_meta_address_with_checksum(scan: &[u8; 32], spend: &[u8; 32]) -> String {
let mut data = [0u8; 64];
data[..32].copy_from_slice(scan);
data[32..].copy_from_slice(spend);
// Add 4-byte checksum
let checksum = &sha256(&sha256(&data))[..4];
let mut with_checksum = [0u8; 68];
with_checksum[..64].copy_from_slice(&data);
with_checksum[64..].copy_from_slice(checksum);
format!("stealth:{}", bs58::encode(&with_checksum).into_string())
}For production use, support hardware wallets for key storage:
- Ledger integration for signing
- Keep spend key on hardware device
- Only expose scan key to software
Add a secure way to verify backups without exposing keys:
pub fn generate_backup_verification_code(keys: &StealthKeys) -> String {
// Generate a short code that proves backup is valid
// Without revealing the actual keys
let hash = sha256(&[
&keys.scan_secret.to_bytes()[..],
&keys.spend_secret.to_bytes()[..],
].concat());
// Return first 8 characters
hex::encode(&hash[..4])
}- Never share your secret keys - Only share your meta-address
- Backup keys securely - Use encrypted storage or hardware wallet
- Verify meta-addresses - Check the address before sending
- Use unique meta-addresses - Consider generating new keys for different purposes
- Monitor announcements - Scan regularly to detect payments
- Run security audits before mainnet deployment
- Use fuzzing to test cryptographic code
- Implement proper logging without exposing sensitive data
- Add monitoring for unusual patterns
- Document threat model clearly
- External security audit of cryptographic implementation
- Formal verification of DKSAP protocol implementation
- Penetration testing of CLI and on-chain program
- Review of all dependencies for vulnerabilities
- Fuzzing of parsing and cryptographic functions
- Review of error handling and edge cases
- Analysis of potential DoS vectors
- Review of key management implementation
- DKSAP Specification
- Solana Security Best Practices
- Curve25519 Security Considerations
- OWASP Cryptographic Guidelines
| Version | Date | Changes |
|---|---|---|
| 1.0 | 2024-01-16 | Initial security analysis |