dark-crystal-web3 0.1.1

Simple CLI for backing up and recovering secrets using Dark Crystal Web3
use clap::{Parser, Subcommand};
use colored::*;
use dark_crystal_web3_core::secret::Secret;
use dark_crystal_web3_core::{recover, share, RecoveredShare, SHARE_LENGTH};
use directories::BaseDirs;
use hex;
use qr_code;
use std::convert::TryInto;
use std::path::PathBuf;

#[derive(Parser, Debug)]
#[clap(author, version, about, long_about = None)]
struct Cli {
    #[clap(subcommand)]
    command: Commands,
}

#[derive(Subcommand, Debug)]
enum Commands {
    /// Create a share set
    #[clap(arg_required_else_help = true)]
    Share {
        /// The secret
        #[clap(short, long)]
        secret: String,

        /// Public encryption keys of recovery partners
        #[clap(short, long)]
        pk: Vec<String>,

        /// A lookup key to identify the secret
        #[clap(short, long)]
        lookup: String,
    },

    /// Recover a secret
    #[clap(arg_required_else_help = true)]
    Combine { shares: Vec<String> },

    /// Serve http interface (if it is installed)
    Serve {
        /// Port to serve UI on
        #[clap(long)]
        port: Option<u16>,
        /// Path of static UI files - defaults to ~/.local/share/dark-crystal-web3
        #[clap(long)]
        ui_path: Option<String>,
    },
}

fn main() {
    let args = Cli::parse();

    match &args.command {
        Commands::Share { secret, pk, lookup } => {
            let mut public_keys: Vec<[u8; 32]> = Vec::new();
            for pk in pk.iter() {
                public_keys.push(hex::decode(&pk).unwrap().try_into().unwrap());
            }

            let secret = Secret::detect_from_string(secret.to_string())
                .to_bytes()
                .unwrap();
            let lookup_key = &lookup.as_bytes().to_vec();

            match share(public_keys, secret.to_vec(), lookup_key.to_vec()) {
                Ok(shares) => {
                    println!("\n{}", shares.green());

                    let qr_code = qr_code::QrCode::new(shares.as_bytes()).unwrap();
                    println!("\n\n{}", qr_code.to_string(true, 3));
                }
                Err(err) => error(format!("{:?}", err)),
            }
        }
        Commands::Combine { shares } => {
            let mut recovered_shares: Vec<RecoveredShare> = Vec::new();
            for share in shares.iter() {
                if share.len() < SHARE_LENGTH * 2 {
                    return error("Share too short".to_string());
                }
                match hex::decode(&share) {
                    Ok(share_bytes) => {
                        recovered_shares.push(RecoveredShare::from_concat(&share_bytes))
                    }
                    Err(_) => return error("Cannot decode hex share".to_string()),
                };
            }

            match recover(recovered_shares) {
                Ok(secret_raw) => match Secret::from_bytes(secret_raw) {
                    Ok(secret) => {
                        let secret_string = match secret {
                            Secret::Raw(bytes) => hex::encode(bytes),
                            Secret::Utf8(string) => string,
                            Secret::Bip39(string) => string,
                        };

                        println!("\n{}", secret_string.green())
                    }
                    // TODO include raw secret here
                    Err(_) => error("Cannot decode secret".to_string()),
                },
                Err(recovery_error) => error(recovery_error.message),
            }
        }
        Commands::Serve { port, ui_path } => {
            let path_option = match ui_path {
                Some(given_path) => Some(PathBuf::from(given_path)),
                None => match BaseDirs::new() {
                    None => None,
                    Some(base_dirs) => Some(base_dirs.data_local_dir().join("dark-crystal-web3")),
                },
            };

            if path_option.is_none() {
                error("Cannot get local data directory.".to_string());
                std::process::exit(1);
            }

            let path = path_option.unwrap();

            // Check the path exists
            match std::fs::metadata(&path) {
                Ok(ui_path) => match ui_path.is_dir() {
                    true => {
                        // Use custom port if given
                        let server = match port {
                            Some(given_port) => file_serve::ServerBuilder::new(&path)
                                .port(*given_port)
                                .build(),
                            None => file_serve::Server::new(&path),
                        };

                        println!("Serving {}", path.display());
                        println!("http://{}", server.addr());
                        println!("Hit CTRL-C to stop");

                        server.serve().unwrap();
                    }
                    _ => {
                        error(format!("Cannot find web ui at {}", path.display()));
                        std::process::exit(1);
                    }
                },
                Err(_) => {
                    error(format!("Cannot find web ui at {}", path.display()));
                    std::process::exit(1);
                }
            }
        }
    }
}

fn error(message: String) {
    println!("{}", message.red());
}