-
Notifications
You must be signed in to change notification settings - Fork 87
Expand file tree
/
Copy pathphrase.rs
More file actions
170 lines (142 loc) · 5.09 KB
/
phrase.rs
File metadata and controls
170 lines (142 loc) · 5.09 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
//! BIP39 mnemonic phrases
use super::{
bits::{BitWriter, IterExt},
language::Language,
};
use crate::{Error, KEY_SIZE, KeyMaterial, Path};
use alloc::string::String;
use rand_core::{CryptoRng, RngCore};
use sha2::{Digest, Sha256};
use zeroize::{Zeroize, Zeroizing};
#[cfg(feature = "bip39")]
use {super::seed::Seed, sha2::Sha512};
/// Number of PBKDF2 rounds to perform when deriving the seed
#[cfg(feature = "bip39")]
const PBKDF2_ROUNDS: u32 = 2048;
/// Source entropy for a BIP39 mnemonic phrase
pub type Entropy = [u8; KEY_SIZE];
/// BIP39 mnemonic phrases: sequences of words representing cryptographic keys.
#[derive(Clone)]
pub struct Phrase {
/// Language
language: Language,
/// Source entropy for this phrase
entropy: Entropy,
/// Mnemonic phrase
phrase: String,
}
impl Phrase {
/// Create a random BIP39 mnemonic phrase.
pub fn random(mut rng: impl RngCore + CryptoRng, language: Language) -> Self {
let mut entropy = Entropy::default();
rng.fill_bytes(&mut entropy);
Self::from_entropy(entropy, language)
}
/// Create a new BIP39 mnemonic phrase from the given entropy
pub fn from_entropy(entropy: Entropy, language: Language) -> Self {
let wordlist = language.wordlist();
let checksum_byte = Sha256::digest(entropy.as_ref()).as_slice()[0];
// First, create a byte iterator for the given entropy and the first byte of the
// hash of the entropy that will serve as the checksum (up to 8 bits for biggest
// entropy source).
//
// Then we transform that into a bits iterator that returns 11 bits at a
// time (as u16), which we can map to the words on the `wordlist`.
//
// Given the entropy is of correct size, this ought to give us the correct word
// count.
let phrase = entropy
.iter()
.chain(Some(&checksum_byte))
.bits()
.map(|bits| wordlist.get_word(bits))
.join(" ");
Phrase {
language,
entropy,
phrase,
}
}
/// Create a new BIP39 mnemonic phrase from the given string.
///
/// The phrase supplied will be checked for word length and validated
/// according to the checksum specified in BIP0039.
///
/// To use the default language, English, (the only one supported by this
/// library and also the only one standardized for BIP39) you can supply
/// `Default::default()` as the language.
pub fn new<S>(phrase: S, language: Language) -> Result<Self, Error>
where
S: AsRef<str>,
{
let phrase = phrase.as_ref();
let wordmap = language.wordmap();
// Preallocate enough space for the longest possible word list
let mut bits = BitWriter::with_capacity(264);
for word in phrase.split(' ') {
bits.push(wordmap.get_bits(word)?);
}
let mut entropy = Zeroizing::new(bits.into_bytes());
if entropy.len() != KEY_SIZE + 1 {
return Err(Error);
}
let actual_checksum = entropy[KEY_SIZE];
// Truncate to get rid of the byte containing the checksum
entropy.truncate(KEY_SIZE);
let expected_checksum = Sha256::digest(&entropy).as_slice()[0];
if actual_checksum != expected_checksum {
return Err(Error);
}
Ok(Self::from_entropy(
entropy.as_slice().try_into().unwrap(),
language,
))
}
/// Get source entropy for this phrase.
pub fn entropy(&self) -> &Entropy {
&self.entropy
}
/// Get the mnemonic phrase as a string reference.
pub fn phrase(&self) -> &str {
&self.phrase
}
/// Language this phrase's wordlist is for
pub fn language(&self) -> Language {
self.language
}
/// Convert this mnemonic phrase's entropy directly into key material.
/// If you are looking for the shortest path between a mnemonic phrase
/// and a key derivation hierarchy, this is it.
///
/// Note: that this does not follow the normal BIP39 derivation, which
/// first applies PBKDF2 along with a secondary password. Use `to_seed`
/// if you are interested in BIP39 compatibility.
pub fn derive_subkey(self, path: impl AsRef<Path>) -> KeyMaterial {
KeyMaterial::from(self).derive_subkey(path)
}
/// Convert this mnemonic phrase into the BIP39 seed value.
#[cfg(feature = "bip39")]
pub fn to_seed(&self, password: &str) -> Seed {
let salt = Zeroizing::new(format!("mnemonic{}", password));
let mut seed = [0u8; Seed::SIZE];
pbkdf2::pbkdf2_hmac::<Sha512>(
self.phrase.as_bytes(),
salt.as_bytes(),
PBKDF2_ROUNDS,
&mut seed,
);
Seed(seed)
}
}
impl From<Phrase> for KeyMaterial {
/// Convert to `KeyMaterial` using an empty password
fn from(phrase: Phrase) -> KeyMaterial {
KeyMaterial::from_bytes(&phrase.entropy).unwrap()
}
}
impl Drop for Phrase {
fn drop(&mut self) {
self.phrase.zeroize();
self.entropy.zeroize();
}
}