use std::io::Write;
use clap::Args;
use ms_codec::{PayloadKind, Tag, Threshold};
use serde_json::to_string;
use zeroize::Zeroizing;
use crate::advisory::{OutputClass, emit_output_class_advisory};
use crate::cmd::encode::resolve_secret_payload;
use crate::error::{CliError, Result};
use crate::format::{SplitJson, render_grouped};
use crate::language::CliLanguage;
#[derive(Args, Debug)]
#[command(group = clap::ArgGroup::new("split_input").required(true).args(["phrase", "hex"]))]
pub struct SplitArgs {
#[arg(long)]
pub phrase: Option<String>,
#[arg(long)]
pub hex: Option<String>,
#[arg(long, default_value = "english")]
pub language: CliLanguage,
#[arg(short = 'k', long = "threshold")]
pub k: u8,
#[arg(short = 'n', long = "shares")]
pub n: usize,
#[arg(long, default_value_t = 5)]
pub group_size: u16,
#[arg(long, default_value = "space", value_parser = crate::format::parse_separator)]
pub separator: char,
#[arg(long)]
pub json: bool,
}
pub fn run(mut args: SplitArgs) -> Result<u8> {
let phrase_arg: Option<Zeroizing<String>> =
std::mem::take(&mut args.phrase).map(Zeroizing::new);
let hex_arg: Option<Zeroizing<String>> = std::mem::take(&mut args.hex).map(Zeroizing::new);
let (payload, _language_for_card) = resolve_secret_payload(
phrase_arg.as_ref().map(|p| p.as_str()),
hex_arg.as_ref().map(|h| h.as_str()),
args.language,
)?;
let threshold = Threshold::new(args.k)?;
let shares = ms_codec::encode_shares(Tag::ENTR, threshold, args.n, &payload)?;
let id = share_id(&shares[0]);
let (kind, language): (&'static str, Option<&'static str>) = match payload.kind() {
PayloadKind::Entr => ("entr", None),
PayloadKind::Mnem => ("mnem", Some(language_str_for_payload(&payload, args.language))),
_ => ("unknown", None),
};
if args.json {
emit_json(&shares, args.k, args.n, &id, kind, language)?;
} else {
emit_text(&shares, args.group_size as usize, args.separator);
}
emit_output_class_advisory(OutputClass::PrivateKeyMaterial, &mut std::io::stderr().lock());
Ok(0)
}
fn share_id(share: &str) -> String {
match share.rfind('1') {
Some(sep) if share.len() >= sep + 6 => share[sep + 2..sep + 6].to_string(),
_ => String::new(),
}
}
fn language_str_for_payload(payload: &ms_codec::Payload, cli_lang: CliLanguage) -> &'static str {
if let ms_codec::Payload::Mnem { language, .. } = payload {
CliLanguage::from_code(*language)
.unwrap_or(cli_lang)
.as_str()
} else {
cli_lang.as_str()
}
}
fn emit_json(
shares: &[String],
k: u8,
n: usize,
id: &str,
kind: &str,
language: Option<&str>,
) -> Result<()> {
let json = SplitJson {
schema_version: "1",
shares: shares.to_vec(),
k,
n,
id: id.to_string(),
kind: kind.to_string(),
language,
};
let s = to_string(&json).map_err(|e| CliError::BadInput(format!("json serialization: {e}")))?;
println!("{s}");
Ok(())
}
fn emit_text(shares: &[String], group_size: usize, separator: char) {
let stdout = std::io::stdout();
let mut out = stdout.lock();
for share in shares {
let _ = writeln!(out, "{}", render_grouped(share, group_size, separator));
}
let stderr = std::io::stderr();
let mut err = stderr.lock();
for (i, _share) in shares.iter().enumerate() {
let _ = writeln!(err, "share {} of {}:", i + 1, shares.len());
}
}