aptos_sdk/account/
mnemonic.rs1use crate::error::{AptosError, AptosResult};
6
7#[derive(Clone)]
22pub struct Mnemonic {
23 phrase: String,
24}
25
26impl Mnemonic {
27 pub fn generate(word_count: usize) -> AptosResult<Self> {
38 let entropy_bytes = match word_count {
39 12 => 16, 15 => 20, 18 => 24, 21 => 28, 24 => 32, _ => {
45 return Err(AptosError::InvalidMnemonic(format!(
46 "invalid word count: {word_count}, must be 12, 15, 18, 21, or 24"
47 )));
48 }
49 };
50
51 let mut entropy = vec![0u8; entropy_bytes];
52 rand::RngCore::fill_bytes(&mut rand::rngs::OsRng, &mut entropy);
53
54 let mnemonic = bip39::Mnemonic::from_entropy(&entropy)
55 .map_err(|e| AptosError::InvalidMnemonic(e.to_string()));
56
57 zeroize::Zeroize::zeroize(&mut entropy);
60
61 let mnemonic = mnemonic?;
62
63 Ok(Self {
64 phrase: mnemonic.to_string(),
65 })
66 }
67
68 pub fn from_phrase(phrase: &str) -> AptosResult<Self> {
74 let _mnemonic = bip39::Mnemonic::parse_normalized(phrase)
76 .map_err(|e| AptosError::InvalidMnemonic(e.to_string()))?;
77
78 Ok(Self {
79 phrase: phrase.to_string(),
80 })
81 }
82
83 pub fn phrase(&self) -> &str {
85 &self.phrase
86 }
87
88 pub fn to_seed(&self) -> AptosResult<[u8; 64]> {
97 self.to_seed_with_passphrase("")
98 }
99
100 pub fn to_seed_with_passphrase(&self, passphrase: &str) -> AptosResult<[u8; 64]> {
108 let mnemonic = bip39::Mnemonic::parse_normalized(&self.phrase).map_err(|e| {
109 AptosError::InvalidMnemonic(format!("internal error: mnemonic re-parse failed: {e}"))
110 })?;
111
112 Ok(mnemonic.to_seed(passphrase))
113 }
114
115 #[cfg(feature = "ed25519")]
123 pub fn derive_ed25519_key(&self, index: u32) -> AptosResult<crate::crypto::Ed25519PrivateKey> {
124 let mut seed = self.to_seed()?;
125 let result = derive_ed25519_from_seed(&seed, index);
126 zeroize::Zeroize::zeroize(&mut seed);
128 let mut key = result?;
129 let private_key = crate::crypto::Ed25519PrivateKey::from_bytes(&key);
130 zeroize::Zeroize::zeroize(&mut key);
132 private_key
133 }
134}
135
136#[cfg(feature = "ed25519")]
140fn derive_ed25519_from_seed(seed: &[u8], index: u32) -> AptosResult<[u8; 32]> {
141 use hmac::{Hmac, Mac};
142 use sha2::Sha512;
143
144 type HmacSha512 = Hmac<Sha512>;
145
146 let mut mac = HmacSha512::new_from_slice(b"ed25519 seed")
148 .map_err(|e| AptosError::KeyDerivation(e.to_string()))?;
149 mac.update(seed);
150 let result = mac.finalize().into_bytes();
151
152 let mut key = [0u8; 32];
153 let mut chain_code = [0u8; 32];
154 key.copy_from_slice(&result[..32]);
155 chain_code.copy_from_slice(&result[32..]);
156
157 let path = [
160 44 | 0x8000_0000, 637 | 0x8000_0000, 0x8000_0000, 0x8000_0000, index | 0x8000_0000, ];
166
167 for child_index in path {
168 let mut data = vec![0u8];
169 data.extend_from_slice(&key);
170 data.extend_from_slice(&child_index.to_be_bytes());
171
172 let mut mac = HmacSha512::new_from_slice(&chain_code)
173 .map_err(|e| AptosError::KeyDerivation(e.to_string()))?;
174 mac.update(&data);
175 let result = mac.finalize().into_bytes();
176
177 key.copy_from_slice(&result[..32]);
178 chain_code.copy_from_slice(&result[32..]);
179
180 zeroize::Zeroize::zeroize(&mut data);
182 }
183
184 zeroize::Zeroize::zeroize(&mut chain_code);
186
187 Ok(key)
188}
189
190impl std::fmt::Debug for Mnemonic {
191 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
192 write!(f, "Mnemonic([REDACTED])")
193 }
194}
195
196#[cfg(test)]
197mod tests {
198 use super::*;
199
200 #[test]
201 fn test_generate_mnemonic() {
202 let mnemonic = Mnemonic::generate(12).unwrap();
203 assert_eq!(mnemonic.phrase().split_whitespace().count(), 12);
204
205 let mnemonic = Mnemonic::generate(24).unwrap();
206 assert_eq!(mnemonic.phrase().split_whitespace().count(), 24);
207 }
208
209 #[test]
210 fn test_invalid_word_count() {
211 assert!(Mnemonic::generate(13).is_err());
212 }
213
214 #[test]
215 fn test_parse_mnemonic() {
216 let phrase = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
217 let mnemonic = Mnemonic::from_phrase(phrase).unwrap();
218 assert_eq!(mnemonic.phrase(), phrase);
219 }
220
221 #[test]
222 fn test_invalid_mnemonic() {
223 assert!(Mnemonic::from_phrase("invalid mnemonic phrase").is_err());
224 }
225
226 #[test]
227 #[cfg(feature = "ed25519")]
228 fn test_derive_ed25519_key() {
229 let phrase = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
230 let mnemonic = Mnemonic::from_phrase(phrase).unwrap();
231
232 let key1 = mnemonic.derive_ed25519_key(0).unwrap();
233 let key2 = mnemonic.derive_ed25519_key(0).unwrap();
234 assert_eq!(key1.to_bytes(), key2.to_bytes());
235
236 let key3 = mnemonic.derive_ed25519_key(1).unwrap();
237 assert_ne!(key1.to_bytes(), key3.to_bytes());
238 }
239}