This is an implementation of BIP0039 in Rust (I used python-mnemonic as a guide). I'd like my first Rust program to be critiqued.
I would like to know if the program is structured correctly, if I've chosen the right constructs to use where, any other general comments on what to improve to make it cleaner and more Rust-y
main.rs:
extern crate getopts; extern crate core; extern crate mnemonic; extern crate "rustc-serialize" as serialize; use mnemonic::Mnemonic; use serialize::hex::{FromHex, ToHex}; use getopts::{reqopt,optflag,getopts,OptGroup}; use std::os; use std::iter::repeat; use std::rand::{OsRng, Rng}; use std::old_io::File; //getopts help message fn print_usage(program: &str, _opts: &[OptGroup]) { println!("Usage: {} [options]", program); println!("-s\t\tSeed"); println!("-h --help\tUsage"); } fn main() { /* start handling opts */ let args: Vec<String> = os::args(); let program = args[0].clone(); let opts = &[ reqopt("s", "seed", "set mnemonic seed", ""), optflag("h", "help", "print this help menu") ]; let matches = match getopts(args.tail(), opts) { Ok(m) => { m } Err(f) => { panic!(f.to_string()) } }; if matches.opt_present("h") { print_usage(program.as_slice(), opts); return; } let seed = match matches.opt_str("s") { Some(x) => x, None => panic!("No seed given"), }; /* end opts, seed value below */ let str_seed:&str = seed.as_slice(); let mut rng = match OsRng::new() { Ok(g) => g, Err(e) => panic!("Failed to obtain OS RNG: {}", e) }; let path = Path::new("src/wordslist/english.txt"); let display = path.display(); let mut file = match File::open(&path) { Err(why) => panic!("couldn't open {}: {}", display, why.desc), Ok(file) => file, }; let words:String = match file.read_to_string() { Err(why) => panic!("couldn't read {}: {}", display, why.desc), Ok(string) => string, }; //generate corner cases for &i in [16us,24,32].iter() { for &n in ["00","7f","80","ff"].iter() { let corner_chars = repeat(n).take(i).collect(); process(corner_chars,str_seed,words.as_slice()); } } //generate random seeds for gen_seed in range(0us,12) { let length = 8 * (gen_seed % 3 + 2); let random_chars:String = rng.gen_ascii_chars().take(length).collect(); process(random_chars,str_seed,words.as_slice()); } } fn process(random_chars:String,str_seed:&str,words:&str) { println!("random characters: {}",random_chars); let mnemonic:Mnemonic = Mnemonic::new(random_chars); let mut mnem_words = Vec::new(); for i in range(0us,mnemonic.binary_hash.len() / 11) { let bin_idx = mnemonic.binary_hash.slice(i*11,(i+1)*11); let idx = std::num::from_str_radix::<isize>(bin_idx, 2).unwrap(); mnem_words.push(words.as_slice().words().nth(idx as usize).unwrap()); //check for better way of doing this } let str_mnemonic = format!("{:?}",mnem_words); println!("mnemonic: {}", str_mnemonic); let key_value = mnemonic.to_seed(str_mnemonic.as_slice(),str_seed); //to_string() on a Vec<&str>? println!("key: {}",key_value.as_slice().to_hex()); }
lib.rs:
extern crate crypto; extern crate "rustc-serialize" as rustc_serialize; use crypto::pbkdf2::{pbkdf2}; use crypto::sha2::{Sha256, Sha512}; use crypto::hmac::Hmac; use crypto::digest::Digest; use std::old_io::File; use rustc_serialize::hex::{FromHex, ToHex}; use std::iter::repeat; static EMPTY:&'static str = "00000000"; //' static PBKDF2_ROUNDS:u32 = 2048; static PBKDF2_KEY_LEN:usize = 64; pub struct Mnemonic { pub binary_hash:String, } impl Mnemonic { pub fn new(chars:String) -> Mnemonic { let h:String = Mnemonic::gen_sha256(chars.as_slice()); //get binary string of random seed let s_two:String = Mnemonic::to_binary(chars.as_bytes()); //get binary str of sha256 hash let h_two:String = Mnemonic::to_binary(h.from_hex().unwrap().as_slice()); let length = s_two.len() / 32; //concatenate the two binary strings together let random_hash:String = s_two + h_two.slice_to( length ).as_slice(); let mn = Mnemonic { binary_hash: random_hash, }; mn } pub fn to_seed(&self,mnemonic:&str, seed_value:&str) -> Vec<u8> { let mut mac = Hmac::new(Sha512::new(),mnemonic.as_bytes()); let mut result:Vec<u8> = repeat(0).take(PBKDF2_KEY_LEN).collect(); let mut salt:String = String::from_str("mnemonic"); salt.push_str(seed_value); pbkdf2(&mut mac, salt.as_bytes(), PBKDF2_ROUNDS, result.as_mut_slice()); result } fn gen_sha256(hashme:&str) -> String { let mut sh = Sha256::new(); sh.input_str(hashme); sh.result_str() } fn to_binary(input:&[u8]) -> String { let mut s_two = String::new(); for &s_byte in input.iter() { let byte_slice = format!("{:b}",s_byte); let mut empty = String::from_str(EMPTY); empty.push_str(byte_slice.as_slice()); let slice = empty.slice_from(empty.len()-8); s_two.push_str(slice); } s_two } }
I'd love to see some comments on my actual use of struct and whether you think I've made a Mnemonic struct type with attributes that make sense.
For example, I would love to have the mnemonic attribute be the actual word mnemonic (Vec<&str> I imagine) but I'm not sure that makes sense given that a mnemonic may be in multiple languages, and that the binary string is the reference for creating the actual word list.
Additionally, In the process function (main.rs) I have a for.. in which splices the binary string into words by looking them up in the wordlist. It seems logical to me that this would be in the mnemonic function, but I ran into an issue of how to pass a wordlist to the mnemonic without copying the whole darn list, passing a reference gave me lifetime errors.
I'd also like to personally thank you Shepmaster for helping me out while I've been learning Rust, I have asked no less than 5 questions based on this very application and I'm certain you've answered all of them.
//'
at the end of lines that introduce an odd number of lifetimes.\$\endgroup\$