Skip to content

Commit 05b53d1

Browse files
committed
WIP: Add pbkfd2 implementation
1 parent 7141b40 commit 05b53d1

3 files changed

Lines changed: 77 additions & 30 deletions

File tree

Cargo.toml

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,7 @@ rocksdb = { version = "0.14", default-features = false, features = ["snappy"], o
3333
cc = { version = ">=1.0.64", optional = true }
3434
socks = { version = "0.3", optional = true }
3535
lazy_static = { version = "1.4", optional = true }
36-
pbkdf2 = { version = "0.10", optional = true }
3736
unicode-normalization = { version = "0.1.19", optional = true }
38-
sha2 = { version = "0.10.2", optional = true }
39-
hmac = { version = "0.12.1", optional = true }
4037

4138
bitcoinconsensus = { version = "0.19.0-3", optional = true }
4239

@@ -63,7 +60,7 @@ compact_filters = ["rocksdb", "socks", "lazy_static", "cc"]
6360
key-value-db = ["sled"]
6461
all-keys = ["keys-bip39"]
6562
rpc = ["bitcoincore-rpc"]
66-
keys-bip39 = ["pbkdf2", "unicode-normalization", "sha2", "hmac"]
63+
keys-bip39 = ["unicode-normalization"]
6764

6865
# Languages for BIP39 mnemonics. English is always included by default
6966
japanese = []

src/keys/bip39/mod.rs

Lines changed: 4 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,6 @@ use bitcoin::{
1818
hashes::{sha256, Hash},
1919
util::bip32,
2020
};
21-
use hmac::Hmac;
22-
use sha2::Sha512;
2321
use unicode_normalization::UnicodeNormalization;
2422

2523
use miniscript::ScriptContext;
@@ -28,6 +26,7 @@ use super::{
2826
any_network, DerivableKey, DescriptorKey, ExtendedKey, GeneratableKey, GeneratedKey, KeyError,
2927
};
3028

29+
mod pbkdf2;
3130
mod wordlists;
3231
pub use wordlists::Language;
3332

@@ -206,32 +205,11 @@ impl Mnemonic {
206205

207206
/// Convert a mnemonic to a seed with an optional passphrase
208207
fn to_seed(&self, passphrase: Option<String>) -> [u8; 64] {
209-
const PBKDF2_ITERATIONS: u32 = 2048;
210-
const PBKDF2_BYTES: usize = 64;
211-
212-
let password = self
213-
.word_iter()
214-
.collect::<Vec<&str>>()
215-
.join(" ")
216-
.nfkd()
217-
.collect::<String>();
218-
let salt = ("mnemonic".to_string() + &passphrase.unwrap_or_else(|| "".to_string()))
219-
.nfkd()
220-
.collect::<String>();
221-
222-
let mut seed = [0u8; PBKDF2_BYTES];
223-
pbkdf2::pbkdf2::<Hmac<Sha512>>(
224-
password.as_bytes(),
225-
salt.as_bytes(),
226-
PBKDF2_ITERATIONS,
227-
&mut seed,
228-
);
229-
230-
seed
208+
pbkdf2::make_seed(self, passphrase)
231209
}
232210

233211
/// Convert a vec of word indices to an iterator of the mnemonic words
234-
fn word_iter(&self) -> impl Iterator<Item = &str> + '_ {
212+
fn word_iter(&self) -> impl Iterator<Item = &str> + Clone + '_ {
235213
let wordlist = self.language.wordlist();
236214
self.words.iter().map(move |&w| wordlist[w as usize])
237215
}
@@ -599,7 +577,7 @@ mod test {
599577
.to_string(),
600578
generated_mnemonic.to_string()
601579
);
602-
assert_eq!(generated_seed[..], seed[..]);
580+
assert_eq!(generated_seed[..], seed[..], "mnemonic: {}", vector[1]);
603581
assert_eq!(generated_privkey.to_string(), xprivkey);
604582
}
605583
}

src/keys/bip39/pbkdf2.rs

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
use std::borrow::Cow;
2+
3+
use bitcoin::hashes::{sha512, Hash, HashEngine, Hmac, HmacEngine};
4+
use unicode_normalization::UnicodeNormalization;
5+
6+
use super::Mnemonic;
7+
8+
const SALT_PREFIX: &str = "mnemonic";
9+
const SEED_LEN: usize = 64;
10+
const PBKDF2_ITERATIONS: u32 = 2048;
11+
12+
// prf
13+
fn pseudo_random_function(key: &[u8], data: &[u8]) -> Hmac<sha512::Hash> {
14+
let mut engine = HmacEngine::new(key);
15+
engine.input(data);
16+
Hmac::from_engine(engine)
17+
}
18+
19+
// c: iterations
20+
// p: password
21+
// s: salt
22+
// i: block index
23+
fn xor_sum(p: &str, s: &str, c: u32, i: u32) -> [u8; sha512::Hash::LEN] {
24+
let mut data = Vec::with_capacity(s.len() + 4);
25+
data.extend_from_slice(s.as_bytes());
26+
data.extend_from_slice(&i.to_be_bytes());
27+
28+
let mut xor_res = [0_u8; sha512::Hash::LEN];
29+
30+
for _ in 0..c {
31+
let u = pseudo_random_function(p.as_bytes(), &data).into_inner();
32+
data.clone_from(&u.to_vec());
33+
34+
// xor
35+
xor_res.iter_mut().zip(&u).for_each(|(a, b)| *a ^= b);
36+
}
37+
38+
xor_res
39+
}
40+
41+
fn pbkd2_hmac_sha512(p: &str, s: &str, c: u32, dk: &mut [u8]) {
42+
for (i, chunk) in dk.chunks_mut(sha512::Hash::LEN).enumerate() {
43+
chunk.copy_from_slice(&xor_sum(p, s, c, (i + 1) as _));
44+
}
45+
}
46+
47+
fn make_password(mnemonic: &Mnemonic) -> String {
48+
mnemonic
49+
.word_iter()
50+
.collect::<Vec<&str>>()
51+
.join(" ")
52+
.nfkd()
53+
.to_string()
54+
}
55+
56+
fn make_salt(passphrase: Option<String>) -> Cow<'static, str> {
57+
let mut salt = Cow::from(SALT_PREFIX);
58+
if let Some(passphrase) = passphrase {
59+
salt.to_mut()
60+
.push_str(&passphrase.nfkd().collect::<String>());
61+
}
62+
salt
63+
}
64+
65+
pub(crate) fn make_seed(mnemonic: &Mnemonic, passphrase: Option<String>) -> [u8; SEED_LEN] {
66+
let password = make_password(mnemonic);
67+
let salt = make_salt(passphrase);
68+
69+
let mut dk = [0_u8; SEED_LEN];
70+
pbkd2_hmac_sha512(&password, &salt, PBKDF2_ITERATIONS, &mut dk);
71+
dk
72+
}

0 commit comments

Comments
 (0)