94 lines
3.2 KiB
Rust
94 lines
3.2 KiB
Rust
|
|
use std::path::PathBuf;
|
|||
|
|
|
|||
|
|
use crate::identity::{default_data_dir, save_identity, Identity, NodeError, IDENTITY_FILE_SIZE};
|
|||
|
|
|
|||
|
|
pub struct InitArgs {
|
|||
|
|
pub data_dir: Option<PathBuf>,
|
|||
|
|
pub mnemonic: Option<String>,
|
|||
|
|
pub entropy_hex: Option<String>,
|
|||
|
|
pub force: bool,
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
pub fn run(args: InitArgs) -> Result<(), NodeError> {
|
|||
|
|
let data_dir = args.data_dir.unwrap_or_else(default_data_dir);
|
|||
|
|
|
|||
|
|
let identity = match (args.mnemonic.as_deref(), args.entropy_hex.as_deref()) {
|
|||
|
|
(Some(_), Some(_)) => {
|
|||
|
|
return Err(NodeError::InvalidArguments(
|
|||
|
|
"укажите либо --mnemonic, либо --entropy, не оба сразу".into(),
|
|||
|
|
))
|
|||
|
|
},
|
|||
|
|
(Some(m), None) => Identity::from_mnemonic(m.trim())?,
|
|||
|
|
(None, Some(hex)) => {
|
|||
|
|
let entropy = parse_entropy_hex(hex)?;
|
|||
|
|
Identity::from_entropy(&entropy)?
|
|||
|
|
},
|
|||
|
|
(None, None) => {
|
|||
|
|
let mut entropy = [0u8; 32];
|
|||
|
|
getrandom::getrandom(&mut entropy)
|
|||
|
|
.map_err(|e| NodeError::InvalidArguments(format!("getrandom: {e}")))?;
|
|||
|
|
Identity::from_entropy(&entropy)?
|
|||
|
|
},
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
let path = save_identity(&data_dir, &identity, args.force)?;
|
|||
|
|
|
|||
|
|
println!("=== montana-node init ===");
|
|||
|
|
println!();
|
|||
|
|
println!("data-dir : {}", data_dir.display());
|
|||
|
|
println!("identity : {}", path.display());
|
|||
|
|
println!("file size : {IDENTITY_FILE_SIZE} bytes");
|
|||
|
|
println!("mode : 0600 (только владелец)");
|
|||
|
|
println!();
|
|||
|
|
println!("--- мнемоника (24 слова — запишите в надёжное место) ---");
|
|||
|
|
print_mnemonic_grid(&identity.mnemonic);
|
|||
|
|
println!();
|
|||
|
|
println!("--- терминальные идентификаторы ---");
|
|||
|
|
println!("account_id : {}", hex_lower(&identity.account_id()));
|
|||
|
|
println!("node_id : {}", hex_lower(&identity.node_id()));
|
|||
|
|
println!(
|
|||
|
|
"master_seed_fp : {} (8-байтный отпечаток, не секрет)",
|
|||
|
|
hex_lower(&identity.master_seed_fingerprint())
|
|||
|
|
);
|
|||
|
|
println!();
|
|||
|
|
println!("Секретные ключи (account_sk/node_sk/mlkem_sk) сохранены в identity.bin");
|
|||
|
|
println!("и не выводятся на экран. Для backup используйте мнемонику выше.");
|
|||
|
|
|
|||
|
|
Ok(())
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
fn parse_entropy_hex(s: &str) -> Result<[u8; 32], NodeError> {
|
|||
|
|
let clean: String = s.chars().filter(|c| !c.is_whitespace()).collect();
|
|||
|
|
if clean.len() != 64 {
|
|||
|
|
return Err(NodeError::InvalidEntropyHex);
|
|||
|
|
}
|
|||
|
|
let mut out = [0u8; 32];
|
|||
|
|
for i in 0..32 {
|
|||
|
|
let byte = u8::from_str_radix(&clean[i * 2..i * 2 + 2], 16)
|
|||
|
|
.map_err(|_| NodeError::InvalidEntropyHex)?;
|
|||
|
|
out[i] = byte;
|
|||
|
|
}
|
|||
|
|
Ok(out)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
fn hex_lower(bytes: &[u8]) -> String {
|
|||
|
|
let mut s = String::with_capacity(bytes.len() * 2);
|
|||
|
|
for b in bytes {
|
|||
|
|
s.push_str(&format!("{b:02x}"));
|
|||
|
|
}
|
|||
|
|
s
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
fn print_mnemonic_grid(mnemonic: &str) {
|
|||
|
|
let words: Vec<&str> = mnemonic.split(' ').collect();
|
|||
|
|
for (i, w) in words.iter().enumerate() {
|
|||
|
|
print!(" [{:>2}] {:<10}", i + 1, w);
|
|||
|
|
if (i + 1) % 4 == 0 {
|
|||
|
|
println!();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
if words.len() % 4 != 0 {
|
|||
|
|
println!();
|
|||
|
|
}
|
|||
|
|
}
|