bip39-cli 0.1.0

A simple CLI tool for generating BIP39 mnemonic phrases
use bip39::Mnemonic;
use clap::{Arg, Command};
use rand::RngCore;
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};

fn main() {
    let matches = Command::new("bip39-cli")
        .version("1.0")
        .author("Your Name")
        .about("Generates BIP39 mnemonic phrases")
        .arg(
            Arg::new("generate")
                .short('g')
                .long("generate")
                .help("Generate a new mnemonic phrase")
                .action(clap::ArgAction::SetTrue),
        )
        .arg(
            Arg::new("words")
                .short('w')
                .long("words")
                .value_name("WORDS")
                .help("Number of words in the mnemonic (12 or 24)")
                .default_value("12")
                .value_parser(clap::value_parser!(u8)),
        )
        .arg(
            Arg::new("entropy")
                .short('e')
                .long("entropy")
                .value_name("STRING")
                .help("Custom entropy string (optional)")
                .required(false),
        )
        .get_matches();

    // If no arguments are provided, default to generate
    let should_generate = matches.get_flag("generate") || std::env::args().len() == 1;

    if should_generate {
        let word_count = *matches.get_one::<u8>("words").unwrap();

        // Validate word count and calculate entropy length
        let entropy_len = match word_count {
            12 => 16, // 128 bits / 8 = 16 bytes
            24 => 32, // 256 bits / 8 = 32 bytes
            _ => {
                eprintln!("Error: Word count must be 12 or 24");
                std::process::exit(1);
            }
        };

        // Generate or use custom entropy
        let (entropy, custom_entropy_used) =
            if let Some(custom_entropy) = matches.get_one::<String>("entropy") {
                // Use custom entropy string
                let mut hasher = DefaultHasher::new();
                custom_entropy.hash(&mut hasher);
                let hash = hasher.finish();

                let mut entropy = vec![0u8; entropy_len];
                for (i, byte) in entropy.iter_mut().enumerate() {
                    *byte = ((hash >> (i % 8)) ^ (hash >> ((i + 7) % 8))) as u8;
                }

                // Fill remaining bytes if needed
                for i in 8..entropy_len {
                    entropy[i] = ((hash >> (i % 8)) ^ custom_entropy.len() as u64) as u8;
                }

                (entropy, Some(custom_entropy.clone()))
            } else {
                // Generate random entropy
                let mut entropy = vec![0u8; entropy_len];
                rand::thread_rng().fill_bytes(&mut entropy);
                (entropy, None)
            };

        // Generate mnemonic from entropy
        match Mnemonic::from_entropy(&entropy) {
            Ok(mnemonic) => {
                let words: Vec<&str> = mnemonic.word_iter().collect();

                println!("🔐 BIP39 Mnemonic Phrase ({} words):", word_count);
                if let Some(custom_str) = custom_entropy_used {
                    println!("Randomness used: Custom string: \"{}\"", custom_str);
                }
                println!();
                println!("Generated {} words:", word_count);
                println!();
                println!("{}", words.join(" "));
                println!();
            }
            Err(e) => {
                eprintln!("Error generating mnemonic: {}", e);
                std::process::exit(1);
            }
        }
    } else {
        // Show help if no generate flag and arguments were provided
        let mut cmd = Command::new("bip39-cli")
            .version("1.0")
            .author("Your Name")
            .about("Generates BIP39 mnemonic phrases")
            .arg(
                Arg::new("generate")
                    .short('g')
                    .long("generate")
                    .help("Generate a new mnemonic phrase")
                    .action(clap::ArgAction::SetTrue),
            )
            .arg(
                Arg::new("words")
                    .short('w')
                    .long("words")
                    .value_name("WORDS")
                    .help("Number of words in the mnemonic (12 or 24)")
                    .default_value("12")
                    .value_parser(clap::value_parser!(u8)),
            )
            .arg(
                Arg::new("entropy")
                    .short('e')
                    .long("entropy")
                    .value_name("STRING")
                    .help("Custom entropy string (optional)")
                    .required(false),
            );
        cmd.print_help().unwrap();
        println!();
    }
}