use std::io::{self, IsTerminal};
use std::process::exit;
use mudra::claim::{self, Claim};
use mudra::{cosmos, seed};
fn tty() -> bool {
io::stdout().is_terminal()
}
fn paint(code: &str, s: &str) -> String {
if tty() { format!("\x1b[{code}m{s}\x1b[0m") } else { s.to_string() }
}
fn dim(s: &str) -> String {
paint("90", s)
}
fn cyan(s: &str) -> String {
paint("36", s)
}
fn green(s: &str) -> String {
paint("32", s)
}
fn yellow(s: &str) -> String {
paint("33", s)
}
fn red(s: &str) -> String {
paint("31", s)
}
fn bold(s: &str) -> String {
paint("1", s)
}
const LOGO: &str = "\
\x1b[31mββββ βββββββ ββββββββββ βββββββ ββββββ \x1b[0m
\x1b[33mβββββ ββββββββ βββββββββββββββββββββββββββ\x1b[0m
\x1b[32mββββββββββββββ ββββββ βββββββββββββββββββ\x1b[0m
\x1b[36mββββββββββββββ ββββββ βββββββββββββββββββ\x1b[0m
\x1b[34mβββ βββ βββββββββββββββββββββββ ββββββ βββ\x1b[0m
\x1b[35mβββ βββ βββββββ βββββββ βββ ββββββ βββ\x1b[0m";
fn banner() {
if !tty() {
return;
}
println!("{LOGO}");
println!("{}", paint("37", " the seal of the neuron"));
println!(
"{}",
dim(
"\n secp256k1 Β· BIP-39/32 Β· coin type 118\n \
ripemd160 β sha256 β bech32 Β· ADR-036 claims\n \
legacy key β Hemera(pubkey) Β· post-quantum ahead\n"
)
);
}
fn help() {
println!(
"{}\n {}\n {}\n {}\n {}\n {}\n\n {}\n {}\n\n {}",
dim("commands"),
format!("{} {}", bold("new"), dim("generate a fresh 24-word wallet")),
format!("{} {}", bold("derive <mnemonicβ¦>"), dim("seed β pussy/bostrom address, pubkey, neuron")),
format!("{} {}", bold("neuron <mnemonicβ¦>"), dim("native neuron id = Hemera(pubkey)")),
format!("{} {}", bold("claim <mnemonicβ¦>"), dim("sign a claim binding the legacy key β neuron")),
format!("{} {}", bold("verify <claim>"), dim("verify a migration claim")),
dim("flags"),
format!("{} {}", bold("--bostrom"), dim("use the bostrom prefix instead of pussy")),
dim("mnemonic words follow the subcommand; default prefix is pussy"),
);
}
fn hex(bytes: &[u8]) -> String {
bytes.iter().map(|b| format!("{b:02x}")).collect()
}
fn die(msg: impl std::fmt::Display) -> ! {
eprintln!(" {}: {msg}", red("error"));
exit(1);
}
fn field(label: &str, value: &str) {
println!(" {} {}", dim(&format!("{label:<9}")), value);
}
fn cmd_derive(mnemonic: &str, hrp: &str) {
let key = seed::cosmos_key(mnemonic, "").unwrap_or_else(|e| die(e));
let pubkey = cosmos::compressed(key.verifying_key());
let addr = cosmos::address(&pubkey, hrp).unwrap_or_else(|e| die(e));
let neuron = claim::neuron_of(&pubkey);
field("account", &cyan(&addr));
field("pubkey", &dim(&hex(&pubkey)));
field("neuron", &green(&hex(&neuron)));
}
fn cmd_neuron(mnemonic: &str) {
let key = seed::cosmos_key(mnemonic, "").unwrap_or_else(|e| die(e));
let neuron = claim::neuron_of(&cosmos::compressed(key.verifying_key()));
println!(" {}", green(&hex(&neuron)));
}
fn cmd_claim(mnemonic: &str, hrp: &str) {
let key = seed::cosmos_key(mnemonic, "").unwrap_or_else(|e| die(e));
let pubkey = cosmos::compressed(key.verifying_key());
let neuron = claim::neuron_of(&pubkey);
let c = claim::create(&key, hrp, neuron).unwrap_or_else(|e| die(e));
field("account", &cyan(&c.address));
field("neuron", &green(&hex(&c.neuron)));
println!(" {} {}", dim("claim"), yellow(&c.encode()));
println!(" {}", dim("β paste this into `mudra verify`"));
}
fn cmd_verify(input: &str, hrp: &str) {
let Some(c) = Claim::decode(input) else {
die("malformed claim (expected: address pubkey neuron signature)");
};
if claim::verify(&c, hrp) {
field("account", &cyan(&c.address));
field("neuron", &green(&hex(&c.neuron)));
println!(" {} {}", green("β"), bold("claim verifies"));
} else {
println!(" {} {}", red("β"), bold("claim does NOT verify"));
exit(1);
}
}
fn cmd_new(hrp: &str) {
let mnemonic = bip39::Mnemonic::generate(24).unwrap_or_else(|e| die(e));
let phrase = mnemonic.to_string();
let key = seed::cosmos_key(&phrase, "").unwrap_or_else(|e| die(e));
let pubkey = cosmos::compressed(key.verifying_key());
let addr = cosmos::address(&pubkey, hrp).unwrap_or_else(|e| die(e));
field("mnemonic", &yellow(&phrase));
field("account", &cyan(&addr));
field("neuron", &green(&hex(&claim::neuron_of(&pubkey))));
println!(" {}", dim("β write the mnemonic down β it is the only key to this account"));
}
fn main() {
let mut args: Vec<String> = std::env::args().skip(1).collect();
let hrp = if let Some(i) = args.iter().position(|a| a == "--bostrom") {
args.remove(i);
cosmos::BOSTROM
} else {
cosmos::PUSSY
};
let cmd = args.first().cloned().unwrap_or_default();
let rest = args.get(1..).unwrap_or(&[]).join(" ");
match cmd.as_str() {
"" | "help" | "-h" | "--help" => {
banner();
help();
}
"new" => cmd_new(hrp),
"derive" | "address" => {
if rest.is_empty() {
die("usage: mudra derive <mnemonicβ¦>");
}
cmd_derive(&rest, hrp);
}
"neuron" => {
if rest.is_empty() {
die("usage: mudra neuron <mnemonicβ¦>");
}
cmd_neuron(&rest);
}
"claim" => {
if rest.is_empty() {
die("usage: mudra claim <mnemonicβ¦>");
}
cmd_claim(&rest, hrp);
}
"verify" => {
if rest.is_empty() {
die("usage: mudra verify <claim>");
}
cmd_verify(&rest, hrp);
}
other => die(format!("unknown command: {other} (try: mudra help)")),
}
}