vanikey 1.0.0

A simple command-line vanity npub generator for Nostr
Documentation
use bech32::FromBase32;
use secp256k1::{KeyPair, Secp256k1, XOnlyPublicKey};
use vanikey::NostrKeyGenerator;

#[test]
fn test_prefix_to_base32_values() {
    let values = NostrKeyGenerator::prefix_to_base32_values("qpzr").unwrap();
    assert_eq!(values, vec![0, 1, 2, 3]);

    let values = NostrKeyGenerator::prefix_to_base32_values("a").unwrap();
    assert_eq!(values, vec![29]);

    let values = NostrKeyGenerator::prefix_to_base32_values("test").unwrap();
    assert_eq!(values, vec![11, 25, 16, 11]);
}

#[test]
fn test_prefix_to_base32_values_invalid_char() {
    let result = NostrKeyGenerator::prefix_to_base32_values("b");
    assert!(result.is_err());

    let result = NostrKeyGenerator::prefix_to_base32_values("hello!");
    assert!(result.is_err());
}

#[test]
fn test_matches_prefix_known_patterns() {
    let secp = Secp256k1::new();
    let mut rng = rand::thread_rng();

    for _ in 0..100 {
        let keypair = NostrKeyGenerator::generate_key(&secp, &mut rng);
        let (pubkey, _) = keypair.x_only_public_key();
        let npub = NostrKeyGenerator::to_npub(&pubkey).unwrap();
        let pubkey_bytes = pubkey.serialize();
        let data_str = &npub[5..];

        for len in 1..=8 {
            let prefix = &data_str[..len];
            let values = NostrKeyGenerator::prefix_to_base32_values(prefix).unwrap();
            assert!(
                NostrKeyGenerator::matches_prefix(&pubkey_bytes, &values),
                "matches_prefix should match for prefix '{}' of npub '{}'",
                prefix,
                npub
            );
        }

        let first_char = data_str.as_bytes()[0];
        let wrong_char = if first_char == b'q' { b'p' } else { b'q' };
        let wrong_prefix = String::from(wrong_char as char);
        let wrong_values = NostrKeyGenerator::prefix_to_base32_values(&wrong_prefix).unwrap();
        let _ = NostrKeyGenerator::matches_prefix(&pubkey_bytes, &wrong_values);
    }
}

#[test]
fn test_matches_prefix_agrees_with_bech32() {
    let secp = Secp256k1::new();
    let mut rng = rand::thread_rng();

    let prefix = "test";
    let prefix_values = NostrKeyGenerator::prefix_to_base32_values(prefix).unwrap();

    for _ in 0..10_000 {
        let keypair = NostrKeyGenerator::generate_key(&secp, &mut rng);
        let (pubkey, _) = keypair.x_only_public_key();
        let pubkey_bytes = pubkey.serialize();
        let npub = NostrKeyGenerator::to_npub(&pubkey).unwrap();

        let bech32_match = npub.starts_with(&format!("npub1{}", prefix));
        let byte_match = NostrKeyGenerator::matches_prefix(&pubkey_bytes, &prefix_values);

        assert_eq!(
            bech32_match, byte_match,
            "matches_prefix disagrees with bech32 for npub '{}'",
            npub
        );
    }
}

#[test]
fn test_matches_prefix_empty() {
    let secp = Secp256k1::new();
    let mut rng = rand::thread_rng();
    let keypair = NostrKeyGenerator::generate_key(&secp, &mut rng);
    let (pubkey, _) = keypair.x_only_public_key();
    let pubkey_bytes = pubkey.serialize();

    let empty_values = NostrKeyGenerator::prefix_to_base32_values("").unwrap();
    assert!(
        NostrKeyGenerator::matches_prefix(&pubkey_bytes, &empty_values),
        "Empty prefix should match any key"
    );
}

#[test]
fn test_generator_creation() {
    let generator = NostrKeyGenerator::new(4);
    assert_eq!(generator.thread_count(), 4);
}

#[test]
fn test_key_generation() {
    let secp = Secp256k1::new();
    let mut rng = rand::thread_rng();
    let keypair = NostrKeyGenerator::generate_key(&secp, &mut rng);
    let (pubkey, _) = XOnlyPublicKey::from_keypair(&keypair);
    assert!(!pubkey.serialize().is_empty());
}

#[test]
fn test_npub_conversion() {
    let secp = Secp256k1::new();
    let mut rng = rand::thread_rng();
    let keypair = NostrKeyGenerator::generate_key(&secp, &mut rng);
    let (pubkey, _) = XOnlyPublicKey::from_keypair(&keypair);
    let npub = NostrKeyGenerator::to_npub(&pubkey).unwrap();

    assert!(npub.starts_with("npub1"));
    assert_eq!(npub.len(), 63);
}

#[test]
fn test_vanity_generation() {
    let generator = NostrKeyGenerator::new(4);
    let prefix = "test";
    let result = generator.find_vanity_key(prefix, None).unwrap();
    assert!(result.npub.starts_with(&format!("npub1{}", prefix)));
}

#[test]
fn test_multiple_threads() {
    let generator = NostrKeyGenerator::new(8);
    let prefix = "a";
    let result = generator.find_vanity_key(prefix, None).unwrap();
    assert!(result.npub.starts_with(&format!("npub1{}", prefix)));
}

#[test]
fn test_generated_key_format() {
    let generator = NostrKeyGenerator::new(1);
    let result = generator.find_vanity_key("a", None).unwrap();

    let (_, nsec_data, _) = bech32::decode(&result.nsec).unwrap();
    let secret_bytes = Vec::<u8>::from_base32(&nsec_data).unwrap();
    let secret_key = secp256k1::SecretKey::from_slice(&secret_bytes).unwrap();

    let secp = Secp256k1::new();
    let keypair = KeyPair::from_secret_key(&secp, &secret_key);
    let (derived_pubkey, _) = keypair.x_only_public_key();
    let derived_npub = NostrKeyGenerator::to_npub(&derived_pubkey).unwrap();

    assert_eq!(result.npub, derived_npub);
}

#[test]
fn test_with_additional_prefixes() {
    let generator = NostrKeyGenerator::new(4);
    let primary_prefix = "test";
    let additional_prefixes = vec!["ace".to_string(), "key".to_string()];
    let result = generator
        .find_vanity_key(primary_prefix, Some(&additional_prefixes))
        .unwrap();

    assert!(result.npub.starts_with(&format!("npub1{}", primary_prefix)));
}

#[test]
fn test_nsec_roundtrip() {
    let secp = Secp256k1::new();
    let mut rng = rand::thread_rng();
    let keypair = NostrKeyGenerator::generate_key(&secp, &mut rng);

    let nsec = NostrKeyGenerator::to_nsec(&keypair).unwrap();
    assert!(nsec.starts_with("nsec1"));

    let (_, nsec_data, _) = bech32::decode(&nsec).unwrap();
    let secret_bytes = Vec::<u8>::from_base32(&nsec_data).unwrap();
    let secret_key = secp256k1::SecretKey::from_slice(&secret_bytes).unwrap();
    let recovered = KeyPair::from_secret_key(&secp, &secret_key);

    let (original_pub, _) = keypair.x_only_public_key();
    let (recovered_pub, _) = recovered.x_only_public_key();
    assert_eq!(original_pub, recovered_pub);
}