parity_bip39/
pbkdf2.rs

1use bitcoin_hashes::{hmac, sha512, Hash, HashEngine};
2
3const SALT_PREFIX: &'static str = "mnemonic";
4
5/// Calculate the binary size of the mnemonic.
6fn mnemonic_byte_len<M>(mnemonic: M) -> usize
7	where M: Iterator<Item = &'static str> + Clone,
8{
9	let mut len = 0;
10	for (i, word) in mnemonic.enumerate() {
11		if i > 0 {
12			len += 1;
13		}
14		len += word.len();
15	}
16	len
17}
18
19/// Wrote the mnemonic in binary form into the hash engine.
20fn mnemonic_write_into<M>(mnemonic: M, engine: &mut sha512::HashEngine)
21	where M: Iterator<Item = &'static str> + Clone,
22{
23	for (i, word) in mnemonic.enumerate() {
24		if i > 0 {
25			engine.input(" ".as_bytes());
26		}
27		engine.input(word.as_bytes());
28	}
29}
30
31/// Create an HMAC engine from the passphrase.
32/// We need a special method because we can't allocate a new byte
33/// vector for the entire serialized mnemonic.
34fn create_hmac_engine<M>(mnemonic: M) -> hmac::HmacEngine<sha512::Hash>
35	where M: Iterator<Item = &'static str> + Clone,
36{
37	// Inner code is borrowed from the bitcoin_hashes::hmac::HmacEngine::new method.
38	let mut ipad = [0x36u8; 128];
39	let mut opad = [0x5cu8; 128];
40	let mut iengine = sha512::Hash::engine();
41	let mut oengine = sha512::Hash::engine();
42
43	if mnemonic_byte_len(mnemonic.clone()) > sha512::HashEngine::BLOCK_SIZE {
44		let hash = {
45			let mut engine = sha512::Hash::engine();
46			mnemonic_write_into(mnemonic, &mut engine);
47			sha512::Hash::from_engine(engine)
48		};
49
50		for (b_i, b_h) in ipad.iter_mut().zip(&hash[..]) {
51			*b_i ^= *b_h;
52		}
53		for (b_o, b_h) in opad.iter_mut().zip(&hash[..]) {
54			*b_o ^= *b_h;
55		}
56	} else {
57		// First modify the first elements from the prefix.
58		let mut cursor = 0;
59		for (i, word) in mnemonic.enumerate() {
60			if i > 0 {
61				ipad[cursor] ^= ' ' as u8;
62				opad[cursor] ^= ' ' as u8;
63				cursor += 1;
64			}
65			for (b_i, b_h) in ipad.iter_mut().skip(cursor).zip(word.as_bytes()) {
66				*b_i ^= *b_h;
67			}
68			for (b_o, b_h) in opad.iter_mut().skip(cursor).zip(word.as_bytes()) {
69				*b_o ^= *b_h;
70			}
71			cursor += word.len();
72			assert!(cursor <= sha512::HashEngine::BLOCK_SIZE, "mnemonic_byte_len is broken");
73		}
74	};
75
76	iengine.input(&ipad[..sha512::HashEngine::BLOCK_SIZE]);
77	oengine.input(&opad[..sha512::HashEngine::BLOCK_SIZE]);
78	hmac::HmacEngine::from_inner_engines(iengine, oengine)
79}
80
81// Method borrowed from rust-bitcoin's endian module.
82#[inline]
83fn u32_to_array_be(val: u32) -> [u8; 4] {
84	let mut res = [0; 4];
85	for i in 0..4 {
86		res[i] = ((val >> (4 - i - 1) * 8) & 0xff) as u8;
87	}
88	res
89}
90
91#[inline]
92fn xor(res: &mut [u8], salt: &[u8]) {
93	debug_assert!(salt.len() >= res.len(), "length mismatch in xor");
94
95	res.iter_mut().zip(salt.iter()).for_each(|(a, b)| *a ^= b);
96}
97
98/// PBKDF2-HMAC-SHA512 implementation using bitcoin_hashes.
99pub(crate) fn pbkdf2<M>(mnemonic: M, unprefixed_salt: &[u8], c: usize, res: &mut [u8])
100	where M: Iterator<Item = &'static str> + Clone,
101{
102	let prf = create_hmac_engine(mnemonic);
103
104	for (i, chunk) in res.chunks_mut(sha512::Hash::LEN).enumerate() {
105		for v in chunk.iter_mut() {
106			*v = 0;
107		}
108
109		let mut salt = {
110			let mut prfc = prf.clone();
111			prfc.input(SALT_PREFIX.as_bytes());
112			prfc.input(unprefixed_salt);
113			prfc.input(&u32_to_array_be((i + 1) as u32));
114
115			let salt = hmac::Hmac::from_engine(prfc).to_byte_array();
116			xor(chunk, &salt);
117			salt
118		};
119
120		for _ in 1..c {
121			let mut prfc = prf.clone();
122			prfc.input(&salt);
123			salt = hmac::Hmac::from_engine(prfc).to_byte_array();
124
125			xor(chunk, &salt);
126		}
127	}
128}