v0.0.9 - TUI: real-time per-word BIP-39 validation in seed entry popup
This commit is contained in:
Generated
+1
-1
@@ -1490,7 +1490,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nsigner"
|
||||
version = "0.0.7"
|
||||
version = "0.0.8"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"chacha20poly1305",
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nsigner"
|
||||
version = "0.0.8"
|
||||
version = "0.0.9"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
description = "Attended Nostr signing daemon — Rust port of n_signer"
|
||||
|
||||
+1
-1
@@ -31,4 +31,4 @@ pub mod error;
|
||||
pub use error::NsignerError;
|
||||
|
||||
/// Version string (matches C NSIGNER_VERSION).
|
||||
pub const VERSION: &str = "v0.0.8";
|
||||
pub const VERSION: &str = "v0.0.9";
|
||||
|
||||
+49
@@ -38,6 +38,11 @@ pub const TRANSPORT_HTTP: u8 = 0x08;
|
||||
/// Matches NSIGNER_QREXEC_SERVICE_NAME in n_signer/src/main.c.
|
||||
pub const QREXEC_SERVICE_NAME: &str = "qubes.NsignerRpc";
|
||||
|
||||
/// Check if a word is in the BIP-39 English wordlist.
|
||||
fn is_valid_bip39_word(word: &str) -> bool {
|
||||
nips::nip006::bip39_wordlist().binary_search(&word).is_ok()
|
||||
}
|
||||
|
||||
/// Number of focusable commands on the main screen:
|
||||
/// 4 transports + Add + Edit + Delete + Clear + Help + Quit.
|
||||
pub const FOCUS_COUNT: usize = 10;
|
||||
@@ -1293,6 +1298,50 @@ impl App {
|
||||
Style::default(),
|
||||
)),
|
||||
];
|
||||
|
||||
// Per-word validation: split the input by whitespace and check
|
||||
// each completed word against the BIP-39 wordlist. The word
|
||||
// currently being typed (last fragment without a trailing space)
|
||||
// is not checked — only space-separated completed words count.
|
||||
let buffer = &self.unlock_input.buffer;
|
||||
let has_trailing_space = buffer.ends_with(' ');
|
||||
let words: Vec<&str> = buffer.split_whitespace().collect();
|
||||
let completed_count = if has_trailing_space {
|
||||
words.len()
|
||||
} else {
|
||||
words.len().saturating_sub(1)
|
||||
};
|
||||
|
||||
if completed_count > 0 {
|
||||
let red = Style::default().fg(ratatui::style::Color::Red);
|
||||
let green = Style::default().fg(ratatui::style::Color::Green);
|
||||
let mut spans: Vec<Span> = Vec::new();
|
||||
for (i, w) in words.iter().take(completed_count).enumerate() {
|
||||
if i > 0 {
|
||||
spans.push(Span::raw(" "));
|
||||
}
|
||||
if is_valid_bip39_word(w) {
|
||||
spans.push(Span::styled(format!("✓{}", w), green));
|
||||
} else {
|
||||
spans.push(Span::styled(format!("✗{}", w), red));
|
||||
}
|
||||
}
|
||||
lines.push(Line::from(""));
|
||||
lines.push(Line::from(spans));
|
||||
}
|
||||
|
||||
// Word count indicator.
|
||||
let total_words = words.len();
|
||||
if total_words > 0 {
|
||||
let valid_counts = [12, 15, 18, 21, 24];
|
||||
let count_note = if valid_counts.contains(&total_words) {
|
||||
format!("Words: {} (valid count)", total_words)
|
||||
} else {
|
||||
format!("Words: {} (need 12/15/18/21/24)", total_words)
|
||||
};
|
||||
lines.push(Line::from(count_note));
|
||||
}
|
||||
|
||||
if self.seed_attempts > 0 {
|
||||
lines.push(Line::from(format!(
|
||||
"Invalid mnemonic. Attempts: {}/10",
|
||||
|
||||
Reference in New Issue
Block a user