qp_human_checkphrase/
lib.rs1use std::{
2 fs::File,
3 io::{self, BufRead, BufReader},
4 path::Path,
5};
6
7use pbkdf2::pbkdf2_hmac;
8use sha2::Sha256;
9
10const WORD_LIST_FILE: &str = "final_wordlist.txt";
12const WORD_COUNT: usize = 2048;
13const SALT: &str = "human-readable-checksum";
14const ITERATIONS: u32 = 40000;
15const CHECKSUM_LEN: usize = 5;
16const KEY_BYTECOUNT: usize = (CHECKSUM_LEN * 11).div_ceil(8);
17
18pub fn load_word_list() -> io::Result<Vec<String>> {
19 if !Path::new(WORD_LIST_FILE).exists() {
20 return Err(io::Error::new(
21 io::ErrorKind::NotFound,
22 format!("Word list file '{}' not found", WORD_LIST_FILE),
23 ));
24 }
25
26 let file = File::open(WORD_LIST_FILE)?;
27 let reader = BufReader::new(file);
28 let words: Vec<String> = reader.lines().collect::<io::Result<_>>()?;
29
30 if words.len() != WORD_COUNT {
31 return Err(io::Error::new(
32 io::ErrorKind::InvalidData,
33 format!("Word list must contain exactly {} words, found {}", WORD_COUNT, words.len()),
34 ));
35 }
36
37 println!("Loaded {} words from {}", words.len(), WORD_LIST_FILE);
38 Ok(words)
39}
40
41pub fn address_to_checksum(address: &str, word_list: &[String]) -> Vec<String> {
42 let mut key = [0u8; KEY_BYTECOUNT];
44 pbkdf2_hmac::<Sha256>(address.as_bytes(), SALT.as_bytes(), ITERATIONS, &mut key);
45 let mut key_int = 0u128; for &byte in key.iter().take(KEY_BYTECOUNT) {
50 key_int = (key_int << 8) | byte as u128;
51 }
52
53 key_int >>= (8 * KEY_BYTECOUNT) % 11;
55 let mut indices = Vec::with_capacity(CHECKSUM_LEN);
60 for i in 0..CHECKSUM_LEN {
61 let shift = (CHECKSUM_LEN - 1 - i) * 11;
62 let index = ((key_int >> shift) & 0x7FF) as usize;
63 indices.push(index);
64 }
65 indices.iter().map(|&i| word_list[i].clone()).collect()
69}
70
71#[cfg(test)]
72mod tests {
73 use super::*;
74
75 #[test]
76 fn test_checksum() -> io::Result<()> {
77 let bip39_list = load_word_list()?;
78 let combos = (bip39_list.len() as u64).pow(CHECKSUM_LEN as u32);
79 println!("Total combos: {}", combos);
80 println!(
81 "Sample: {:?} ... {:?}",
82 &bip39_list[..CHECKSUM_LEN],
83 &bip39_list[bip39_list.len() - CHECKSUM_LEN..]
84 );
85
86 let test_addresses = [
87 "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa", "1A1zP1eP5QGefi2DMPTfTL5SLmv7DixfNa", "0x742d35Cc6634C0532925a3b844Bc9e7595f5bE21", "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY", "cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02", "bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq", "qzk7h3xH4Fmv2RqKpN8sT5jW9cY6gB1dL3mX0vQwEaUoZrJtS", "qzkABCDEF123456789abcdefGHIJKLMNOPQRSTUVWXYZ000001", "qzkXyZ987654321FeDcBaAbCdEfGhIjKlMnOpQrStUvWxYz99", ];
97 for addr in test_addresses {
98 let four_words = address_to_checksum(addr, &bip39_list);
99 println!("Address: {}", addr);
100 println!("Checksum: {}", four_words.join("-"));
101 }
102
103 Ok(())
104 }
105}