1use anyhow::{bail, Result};
3use num_bigint::BigUint;
4use num_traits::One;
5use zeroize::Zeroizing;
6
7mod words;
8mod capi;
9
10#[derive(Clone)]
40pub struct SeedEnc {
41 bytes: Zeroizing<[u8; Self::BYTES_LEN]>,
42}
43
44fn nums_for_words(w: &str) -> Result<[u16; SeedEnc::WORD_COUNT]> {
45 let splitwords = w.split(" ").collect::<Vec<_>>();
46 if splitwords.len() != SeedEnc::WORD_COUNT {
47 bail!(
48 "Expected a {} word seed, got {}",
49 SeedEnc::WORD_COUNT,
50 splitwords.len()
51 );
52 }
53 let mut nums = [0_u16; SeedEnc::WORD_COUNT];
54 let mut offending_word = None;
55 for lang in words::LANGUAGES {
56 for (word, i) in splitwords.iter().zip(0..) {
57 if let Some(n) = lang.num_for_word(word) {
58 nums[i] = n;
59 if i == SeedEnc::WORD_COUNT - 1 {
60 return Ok(nums);
61 }
62 } else {
63 offending_word = Some(word);
64 break;
65 }
66 }
67 }
68 bail!(
69 "No language could be found which matched: {:?} in languages: {:?}",
70 offending_word,
71 words::LANGUAGES.iter().map(|l|l.name).collect::<Vec<_>>()
72 );
73}
74
75
76fn date_from_ts(ts: u64) -> std::time::SystemTime {
78 use std::ops::Add;
79 std::time::SystemTime::UNIX_EPOCH.add(std::time::Duration::from_secs(ts))
80}
81
82impl SeedEnc {
83 const BEGINNING_OF_TIME: u64 = 1586276691;
87
88 pub fn words(&self, lang_name: &str) -> Result<String> {
90 if let Some(lang) = words::language(lang_name) {
91 let mut words = [""; Self::WORD_COUNT];
92 for (n, i) in self.nums().iter().zip(0..) {
93 words[i] = lang.word_for_num(*n).unwrap();
94 }
95 Ok(words.join(" "))
96 } else {
97 bail!("Language {} not found", lang_name);
98 }
99 }
100 pub fn from_words(w: &str) -> Result<Self> {
102 let nums = nums_for_words(w)?;
103 Self::from_nums(nums)
104 }
105 fn get_unused(&self) -> u8 {
107 self.bytes[0] >> 5
108 }
109 fn get_ver(&self) -> u8 {
111 (self.bytes[0] >> 1) & 0x0f
112 }
113 pub fn is_encrypted(&self) -> bool {
115 self.bytes[0] & 0x01 == 0x01
116 }
117 pub fn decrypt(&self, passphrase: Option<&[u8]>, force: bool) -> Result<Seed> {
123 let mut copy = self.clone();
124 if passphrase.is_some() && self.is_encrypted() {
125 cipher(&mut copy.bytes[2..], passphrase);
126 } else if self.is_encrypted() {
127 bail!("This seed is encrypted and requires a passphrase to decrypt");
128 }
129 let out = Seed::new_raw(©.bytes[2..]);
130 if !force {
131 if out.get_bday() < Self::BEGINNING_OF_TIME {
132 bail!(concat!(
133 "This seed has a declared birthday of {:?} which is older than the ",
134 "time when this seed algorithm was first created, the password or ",
135 "seed words are probably incorrect, to override this message set force ",
136 "to true."), date_from_ts(out.get_bday()));
137 } else if out.get_bday() > now_sec() {
138 bail!(concat!(
139 "This seed has a declated birthday of {:?} which is in the future ",
140 "the seed or password protecting it are probably incorrect. To override ",
141 "this message set force to true."), date_from_ts(out.get_bday()));
142 }
143 }
144 Ok(Seed::new_raw(©.bytes[2..]))
145 }
146
147 const BYTES_LEN: usize = 21;
151
152 const VER: u8 = 0;
154
155 const EXPECT_UNUSED: u8 = 1;
157
158 const WORD_COUNT: usize = 15;
160
161 fn from_nums(nums: [u16; Self::WORD_COUNT]) -> Result<Self> {
162 let mut b = BigUint::one();
163 for n in nums.iter().rev() {
164 b <<= 11;
165 b += *n;
166 }
167 let bytes = b.to_bytes_be();
168 if bytes.len() != Self::BYTES_LEN {
169 bail!("invalid seed: unexpected byte length");
170 }
171 let mut out = SeedEnc {
172 bytes: Zeroizing::new([0_u8; Self::BYTES_LEN]),
173 };
174 out.bytes.copy_from_slice(&bytes[..]);
175 if out.get_unused() != Self::EXPECT_UNUSED {
176 bail!("Invalid seed: Wrong bit pattern");
177 }
178 out.put_unused(0);
180 if out.get_ver() != Self::VER {
181 bail!("Invalid seed: Unknown version [{}]", out.get_ver())
182 }
183 if out.get_csum() != out.compute_csum() {
184 bail!(
185 "Invalid seed: Checksum mismatch: Declared: [{}], Computed: [{}]",
186 out.get_csum(),
187 out.compute_csum()
188 )
189 }
190 Ok(out)
191 }
192 fn nums(&self) -> [u16; Self::WORD_COUNT] {
193 let mut copy = self.clone();
194 copy.put_unused(Self::EXPECT_UNUSED);
195 let mut b = BigUint::from_bytes_be(©.bytes[..]);
196 let mut out = [0_u16; Self::WORD_COUNT];
197 for i in 0..Self::WORD_COUNT {
198 out[i] = (b.iter_u32_digits().next().unwrap() & 2047) as u16;
199 b >>= 11;
200 }
201 assert!(b.is_one());
202 out
203 }
204 fn put_unused(&mut self, u: u8) {
205 self.bytes[0] &= 31;
206 self.bytes[0] |= u << 5;
207 }
208 fn put_ver(&mut self, v: u8) {
209 self.bytes[0] = (self.bytes[0] & 0x01) | ((v & 0x0f) << 1);
210 }
211 fn put_encrypted(&mut self, e: bool) {
212 self.bytes[0] &= 0x1e;
213 if e {
214 self.bytes[0] |= 0x01
215 }
216 }
217 fn get_csum(&self) -> u8 {
218 self.bytes[1]
219 }
220 fn put_csum(&mut self, csum: u8) {
221 self.bytes[1] = csum;
222 }
223 fn compute_csum(&self) -> u8 {
224 let mut b2b = blake2b_simd::Params::new().hash_length(32).to_state();
225 let mut copy = self.clone();
227 copy.put_csum(0);
228 b2b.update(©.bytes[..]);
229 let res = b2b.finalize();
230 res.as_bytes()[0]
231 }
232}
233
234fn now_sec() -> u64 {
235 use std::time::{SystemTime, UNIX_EPOCH};
236 let start = SystemTime::now();
237 start.duration_since(UNIX_EPOCH).unwrap().as_secs()
238}
239
240const ARGON_SALT: &[u8] = b"pktwallet seed 0";
245
246const ARGON_ITERATIONS: u32 = 32;
247const ARGON_THREADS: u32 = 8;
248const ARGON_MEMORY: u32 = 256 * 1024; const ARGON_HASH_LEN: u32 = 19;
250
251fn cipher(data: &mut [u8], passphrase: Option<&[u8]>) {
252 if let Some(passphrase) = passphrase {
253 let hash = argon2::hash_raw(passphrase, ARGON_SALT, &argon2::Config{
267 ad: &[],
268 hash_length: ARGON_HASH_LEN,
269 lanes: ARGON_THREADS,
270 mem_cost: ARGON_MEMORY,
271 secret: &[],
272 thread_mode: argon2::ThreadMode::Parallel,
273 time_cost: ARGON_ITERATIONS,
274 variant: argon2::Variant::Argon2id,
275 version: argon2::Version::Version13,
276 }).unwrap();
277
278 for i in 0..data.len() {
279 data[i] ^= hash[i];
280 }
281 }
282}
283
284#[derive(Clone)]
292pub struct Seed {
293 pub bytes: Zeroizing<[u8; Self::BYTES_LEN]>,
294}
295
296impl Seed {
297 pub const BYTES_LEN: usize = 19;
299
300 pub fn new(bytes: &[u8]) -> Self {
302 Self::new_bday(bytes, now_sec())
303 }
304 pub fn new_bday(bytes: &[u8], birthday: u64) -> Self {
306 let mut out = Self {
307 bytes: Zeroizing::new([0_u8; Self::BYTES_LEN]),
308 };
309 out.bytes[2..].copy_from_slice(bytes);
310 out.put_bday(birthday);
311 out
312 }
313 pub fn new_raw(bytes: &[u8]) -> Self {
315 let mut out = Self {
316 bytes: Zeroizing::new([0_u8; Self::BYTES_LEN]),
317 };
318 out.bytes.copy_from_slice(bytes);
319 out
320 }
321 pub fn get_bday(&self) -> u64 {
323 let mut bday_bytes = [0_u8; 2];
324 bday_bytes.copy_from_slice(&self.bytes[0..2]);
325 let day = u16::from_be_bytes(bday_bytes);
326 (day as u64) * 60 * 60 * 24
327 }
328 pub fn put_bday(&mut self, unix: u64) {
330 let day_bytes = ((unix / (60 * 60 * 24)) as u16).to_be_bytes();
331 self.bytes[0..2].copy_from_slice(&day_bytes[..]);
332 }
333 pub fn encrypt(&self, passphrase: Option<&[u8]>) -> SeedEnc {
336 let mut out = SeedEnc {
337 bytes: Zeroizing::new([0_u8; SeedEnc::BYTES_LEN]),
338 };
339 out.bytes[2..].copy_from_slice(&self.bytes[..]);
340 cipher(&mut out.bytes[2..], passphrase);
341 out.put_ver(SeedEnc::VER);
342 out.put_encrypted(passphrase.is_some());
343 out.put_csum(out.compute_csum());
344 out
345 }
346}
347
348#[cfg(test)]
349mod tests {
350 use super::Seed;
351 use super::SeedEnc;
352 use anyhow::Result;
353 const WORDS: &str =
354 "mom blanket bulk draw clip wolf bread erupt merry skin cable infant word exchange animal";
355 const BDAY: u64 = 1629936000;
356 const SECRET_HEX: &str = "49b1ad8001b3c4813d50c087c5a4e206aeb111";
357 const SEED_PASS: &[u8] = b"password";
358
359 #[test]
360 fn test_vec() -> Result<()> {
361 let se = SeedEnc::from_words(WORDS)?;
362 let seed = se.decrypt(Some(SEED_PASS), false)?;
363 let seed_hex = hex::encode(&seed.bytes[..]);
364 assert_eq!(seed.get_bday(), BDAY);
365 assert_eq!(seed_hex, SECRET_HEX);
366 Ok(())
367 }
368
369 #[test]
370 fn test_nums_from_words() -> Result<()> {
371 let expect_nums = [
372 1142_u16, 186, 240, 531, 346, 2022, 219, 615, 1117, 1620, 255, 922, 2027, 629, 72,
373 ];
374 let nums = super::nums_for_words(WORDS)?;
375 assert_eq!(nums, expect_nums);
376 Ok(())
377 }
378
379 #[test]
380 fn test_enc_from_words() -> Result<()> {
381 let se = SeedEnc::from_words(WORDS)?;
382 let seed_hex = hex::encode(&se.bytes[..]);
383 println!("seed_enc_hex = {}", seed_hex);
384 assert_eq!(seed_hex, "01213afeb7343ff2a45d4ce36ff315a4263c05d476");
385 Ok(())
386 }
387
388 #[test]
389 fn test_dec_to_words() {
390 let seed = Seed::new_raw(&hex::decode(SECRET_HEX).unwrap()[..]);
391 let se = seed.encrypt(Some(SEED_PASS));
392 assert_eq!(se.words("english").unwrap(), WORDS);
393 }
394}