use crate::error::{Result, StealthError};
use crate::keys::StealthMetaAddress;
use solana_sdk::signature::{keypair_from_seed, Keypair};
use solana_sdk::signer::Signer;
pub struct StealthKeypair {
keypair: Keypair,
}
impl StealthKeypair {
pub fn derive(
meta: &StealthMetaAddress,
ephemeral_pubkey: &[u8; 32],
) -> Result<Self> {
let spend_key_bytes = meta.derive_spend_key(ephemeral_pubkey)?;
let keypair = keypair_from_seed(&spend_key_bytes)
.map_err(|e| StealthError::CryptoError(format!("Failed to derive keypair: {}", e)))?;
Ok(Self { keypair })
}
pub fn address(&self) -> solana_sdk::pubkey::Pubkey {
self.keypair.pubkey()
}
pub fn private_key(&self) -> [u8; 32] {
let bytes = self.keypair.to_bytes();
let mut secret = [0u8; 32];
secret.copy_from_slice(&bytes[..32]);
secret
}
pub fn to_solana_keypair(&self) -> Result<Keypair> {
Ok(self.keypair.insecure_clone())
}
pub fn as_keypair(&self) -> &Keypair {
&self.keypair
}
}
impl std::fmt::Debug for StealthKeypair {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("StealthKeypair")
.field("address", &self.address().to_string())
.field("private_key", &"[REDACTED]")
.finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::address::StealthPayment;
#[test]
fn test_derive_spend_key() {
let meta = StealthMetaAddress::generate();
let public_meta = meta.public_meta_address();
let payment = StealthPayment::create(&public_meta, 1_000_000_000).unwrap();
let stealth_keypair = StealthKeypair::derive(&meta, &payment.ephemeral_pubkey).unwrap();
assert_eq!(
stealth_keypair.address(),
payment.stealth_address,
"Derived address should match payment's stealth address"
);
}
#[test]
fn test_to_solana_keypair() {
let meta = StealthMetaAddress::generate();
let public_meta = meta.public_meta_address();
let payment = StealthPayment::create(&public_meta, 1_000_000_000).unwrap();
let stealth_keypair = StealthKeypair::derive(&meta, &payment.ephemeral_pubkey).unwrap();
let solana_keypair = stealth_keypair.to_solana_keypair().unwrap();
assert_eq!(
solana_keypair.pubkey().to_bytes(),
stealth_keypair.address().to_bytes()
);
}
#[test]
fn test_different_ephemeral_different_keypair() {
let meta = StealthMetaAddress::generate();
let public_meta = meta.public_meta_address();
let payment1 = StealthPayment::create(&public_meta, 1_000_000_000).unwrap();
let payment2 = StealthPayment::create(&public_meta, 2_000_000_000).unwrap();
let keypair1 = StealthKeypair::derive(&meta, &payment1.ephemeral_pubkey).unwrap();
let keypair2 = StealthKeypair::derive(&meta, &payment2.ephemeral_pubkey).unwrap();
assert_ne!(
keypair1.private_key(), keypair2.private_key(),
"Different payments should produce different keypairs"
);
assert_ne!(keypair1.address(), keypair2.address());
}
}