use std::io::{self, Read};
use std::process::ExitCode;
use clap::{Parser, Subcommand};
use agent_rooms::canonical::{canonical_json, parse, sha256_hex};
use agent_rooms::keys::{generate_keypair, pubkey_from_hex, sig_from_hex, sign, to_hex, verify};
#[derive(Parser)]
#[command(
name = "parley",
about = "Offline parley protocol utilities (Rust port).",
version
)]
struct Cli {
#[command(subcommand)]
cmd: Cmd,
}
#[derive(Subcommand)]
enum Cmd {
Keygen,
Canonical {
#[arg(long)]
hash: bool,
},
Sign {
#[arg(long)]
sk: String,
},
Verify {
#[arg(long)]
pk: String,
#[arg(long)]
sig: String,
},
}
fn read_stdin() -> io::Result<String> {
let mut s = String::new();
io::stdin().read_to_string(&mut s)?;
Ok(s)
}
fn main() -> ExitCode {
let cli = Cli::parse();
match run(cli) {
Ok(()) => ExitCode::SUCCESS,
Err(e) => {
eprintln!("error: {e}");
ExitCode::FAILURE
}
}
}
fn run(cli: Cli) -> Result<(), String> {
match cli.cmd {
Cmd::Keygen => {
let (sk, pk) = generate_keypair();
println!("sk: {}", to_hex(&sk));
println!("pk: {}", to_hex(&pk));
Ok(())
}
Cmd::Canonical { hash } => {
let json_in = read_stdin().map_err(|e| e.to_string())?;
let v = parse(&json_in).map_err(|e| e.to_string())?;
let bytes = canonical_json(&v);
io::Write::write_all(&mut io::stdout(), &bytes).map_err(|e| e.to_string())?;
println!();
if hash {
println!("{}", sha256_hex(&v));
}
Ok(())
}
Cmd::Sign { sk } => {
let json_in = read_stdin().map_err(|e| e.to_string())?;
let v = parse(&json_in).map_err(|e| e.to_string())?;
let bytes = canonical_json(&v);
let sk_bytes = hex::decode(&sk).map_err(|e| format!("invalid sk hex: {e}"))?;
if sk_bytes.len() != 32 {
return Err("sk must be 32 bytes (64 hex chars)".into());
}
let sk_arr: [u8; 32] = sk_bytes.try_into().unwrap();
let sig = sign(&sk_arr, &bytes);
println!("{}", to_hex(&sig));
Ok(())
}
Cmd::Verify { pk, sig } => {
let json_in = read_stdin().map_err(|e| e.to_string())?;
let v = parse(&json_in).map_err(|e| e.to_string())?;
let bytes = canonical_json(&v);
let pk_arr = pubkey_from_hex(&pk).map_err(|e| e.to_string())?;
let sig_arr = sig_from_hex(&sig).map_err(|e| e.to_string())?;
if verify(&pk_arr, &bytes, &sig_arr) {
println!("ok");
Ok(())
} else {
Err("signature does not verify".into())
}
}
}
}