Compare commits

..

2 Commits

5 changed files with 136 additions and 9 deletions

1
Cargo.lock generated
View File

@ -1078,6 +1078,7 @@ name = "keyfork-prompt"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"crossterm", "crossterm",
"keyfork-mnemonic-util",
"thiserror", "thiserror",
] ]

View File

@ -64,8 +64,13 @@ impl Wordlist {
Arc::new(self) Arc::new(self)
} }
/// Determine whether the Wordlist contains a given word.
pub fn contains(&self, word: &str) -> bool {
self.0.iter().any(|w| w.as_str() == word)
}
/// Given an index, get a word from the wordlist. /// Given an index, get a word from the wordlist.
fn get_word(&self, word: usize) -> Option<&String> { pub fn get_word(&self, word: usize) -> Option<&String> {
self.0.get(word) self.0.get(word)
} }

View File

@ -5,6 +5,11 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[features]
default = ["mnemonic"]
mnemonic = ["keyfork-mnemonic-util"]
[dependencies] [dependencies]
crossterm = { version = "0.27.0", default-features = false, features = ["use-dev-tty", "events"] } crossterm = { version = "0.27.0", default-features = false, features = ["use-dev-tty", "events"] }
keyfork-mnemonic-util = { version = "0.1.0", path = "../keyfork-mnemonic-util", optional = true }
thiserror = "1.0.51" thiserror = "1.0.51"

View File

@ -1,12 +1,14 @@
use std::io::{stdin, stdout}; use std::{io::{stdin, stdout}, str::FromStr};
use keyfork_prompt::*; use keyfork_prompt::*;
use keyfork_mnemonic_util::Mnemonic;
pub fn main() -> Result<()> { pub fn main() -> Result<()> {
let mut mgr = PromptManager::new(stdin(), stdout())?; let mut mgr = PromptManager::new(stdin(), stdout())?;
mgr.prompt_input("Mnemonic: ")?;
mgr.prompt_message("Please press enter.")?;
mgr.prompt_passphrase("Passphrase: ")?; mgr.prompt_passphrase("Passphrase: ")?;
mgr.prompt_message("Please press space bar.")?; let string = mgr.prompt_wordlist("Mnemonic: ", &Default::default())?;
let mnemonic = Mnemonic::from_str(&string).unwrap();
let entropy = mnemonic.entropy();
mgr.prompt_message(&format!("Your entropy is: {entropy:X?}"))?;
Ok(()) Ok(())
} }

View File

@ -3,6 +3,9 @@ use std::{
os::fd::AsRawFd, os::fd::AsRawFd,
}; };
#[cfg(feature = "mnemonic")]
use keyfork_mnemonic_util::Wordlist;
use crossterm::{ use crossterm::{
cursor, cursor,
event::{read, Event, KeyCode}, event::{read, Event, KeyCode},
@ -69,8 +72,8 @@ where
Ok(line) Ok(line)
} }
// TODO: return secrecy::Secret<String> #[cfg(feature = "mnemonic")]
pub fn prompt_passphrase(&mut self, prompt: &str) -> Result<String> { pub fn prompt_wordlist(&mut self, prompt: &str, wordlist: &Wordlist) -> Result<String> {
let mut terminal = AlternateScreen::new(&mut self.write)?; let mut terminal = AlternateScreen::new(&mut self.write)?;
let mut terminal = RawMode::new(&mut terminal)?; let mut terminal = RawMode::new(&mut terminal)?;
@ -78,7 +81,9 @@ where
.queue(terminal::Clear(terminal::ClearType::All))? .queue(terminal::Clear(terminal::ClearType::All))?
.queue(cursor::MoveTo(0, 0))?; .queue(cursor::MoveTo(0, 0))?;
let mut lines = prompt.lines().peekable(); let mut lines = prompt.lines().peekable();
let mut prefix_length = 0;
while let Some(line) = lines.next() { while let Some(line) = lines.next() {
prefix_length = line.len();
terminal.queue(Print(line))?; terminal.queue(Print(line))?;
if lines.peek().is_some() { if lines.peek().is_some() {
terminal terminal
@ -88,16 +93,123 @@ where
} }
terminal.flush()?; terminal.flush()?;
let (mut cols, mut _rows) = terminal::size()?;
let mut input = String::new();
loop {
match read()? {
Event::Resize(new_cols, new_rows) => {
cols = new_cols;
_rows = new_rows;
}
Event::Key(k) => match k.code {
KeyCode::Enter => {
input.push('\n');
break;
}
KeyCode::Backspace => {
input.pop();
}
KeyCode::Char(' ') => {
if !input.chars().rev().next().is_some_and(char::is_whitespace) {
input.push(' ');
}
}
KeyCode::Char(c) => {
input.push(c);
}
_ => (),
},
_ => (),
}
let usable_space = cols as usize - prefix_length - 1;
terminal
.queue(cursor::MoveToColumn(prefix_length as u16))?
.queue(terminal::Clear(terminal::ClearType::UntilNewLine))?
.flush()?;
let printable_input_start = if input.len() > usable_space {
let start_index = input.len() - usable_space;
input
.chars()
.enumerate()
.skip(start_index)
.skip_while(|(_, ch)| !ch.is_whitespace())
.next()
.expect("any printable character")
.0
} else {
0
};
let printable_input = &input[printable_input_start..];
let mut iter = printable_input.split_whitespace().peekable();
while let Some(word) = iter.next() {
if wordlist.contains(word) {
terminal.queue(PrintStyledContent(word.green()))?;
} else {
terminal.queue(PrintStyledContent(word.red()))?;
}
if iter.peek().is_some()
|| printable_input
.chars()
.rev()
.next()
.is_some_and(char::is_whitespace)
{
terminal.queue(Print(" "))?;
}
}
terminal.flush()?;
}
Ok(input)
}
// TODO: return secrecy::Secret<String>
pub fn prompt_passphrase(&mut self, prompt: &str) -> Result<String> {
let mut terminal = AlternateScreen::new(&mut self.write)?;
let mut terminal = RawMode::new(&mut terminal)?;
terminal
.queue(terminal::Clear(terminal::ClearType::All))?
.queue(cursor::MoveTo(0, 0))?;
let mut lines = prompt.lines().peekable();
let mut prefix_length = 0;
while let Some(line) = lines.next() {
prefix_length = line.len();
terminal.queue(Print(line))?;
if lines.peek().is_some() {
terminal
.queue(cursor::MoveDown(1))?
.queue(cursor::MoveToColumn(0))?;
}
}
terminal.flush()?;
let (mut cols, mut _rows) = terminal::size()?;
let mut passphrase = String::new(); let mut passphrase = String::new();
loop { loop {
match read()? { match read()? {
Event::Resize(new_cols, new_rows) => {
cols = new_cols;
_rows = new_rows;
}
Event::Key(k) => match k.code { Event::Key(k) => match k.code {
KeyCode::Enter => { KeyCode::Enter => {
passphrase.push('\n'); passphrase.push('\n');
break; break;
} }
KeyCode::Backspace => { KeyCode::Backspace => {
if passphrase.pop().is_some() { let passphrase_len = passphrase.len();
if passphrase.pop().is_some()
&& prefix_length + passphrase_len < cols as usize
{
terminal terminal
.queue(cursor::MoveLeft(1))? .queue(cursor::MoveLeft(1))?
.queue(Print(" "))? .queue(Print(" "))?
@ -106,7 +218,9 @@ where
} }
} }
KeyCode::Char(c) => { KeyCode::Char(c) => {
if prefix_length + passphrase.len() < (cols - 1) as usize {
terminal.queue(Print("*"))?.flush()?; terminal.queue(Print("*"))?.flush()?;
}
passphrase.push(c); passphrase.push(c);
} }
_ => (), _ => (),