neo3/neo_wallets/
bip39_account.rs1use crate::{
2 crypto::KeyPair,
3 neo_protocol::{Account, AccountTrait},
4};
5use bip39::{Language, Mnemonic};
6use sha2::{Digest, Sha256};
7
8#[derive(Debug)]
38pub struct Bip39Account {
39 account: Account,
41
42 mnemonic: String,
44}
45
46impl Bip39Account {
47 pub fn mnemonic(&self) -> &str {
49 &self.mnemonic
50 }
51
52 pub fn account(&self) -> &Account {
54 &self.account
55 }
56 pub fn create(password: &str) -> Result<Self, Box<dyn std::error::Error>> {
79 let mut rng = bip39::rand::thread_rng();
80 let mnemonic =
81 Mnemonic::generate_in_with(&mut rng, Language::English, 24).map_err(|e| {
82 Box::<dyn std::error::Error>::from(format!("Failed to generate mnemonic: {e}"))
83 })?;
84 let seed = mnemonic.to_seed(password);
85
86 let mut hasher = Sha256::new();
87 hasher.update(&seed);
88 let private_key = hasher.finalize();
89
90 let key_pair = KeyPair::from_private_key(private_key.as_ref()).map_err(|e| {
91 Box::<dyn std::error::Error>::from(format!("Failed to create key pair: {e}"))
92 })?;
93 let account = Account::from_key_pair(key_pair.clone(), None, None).map_err(|e| {
94 Box::<dyn std::error::Error>::from(format!(
95 "Failed to create account from key pair: {}",
96 e
97 ))
98 })?;
99
100 Ok(Self { account, mnemonic: mnemonic.to_string() })
101 }
102
103 pub fn from_bip39_mnemonic(
124 password: &str,
125 mnemonic: &str,
126 ) -> Result<Self, Box<dyn std::error::Error>> {
127 let mnemonic = Mnemonic::parse_in(Language::English, mnemonic)?;
128 let seed = mnemonic.to_seed(password);
129
130 let mut hasher = Sha256::new();
131 hasher.update(&seed);
132 let private_key = hasher.finalize();
133
134 let key_pair = KeyPair::from_private_key(private_key.as_ref()).map_err(|e| {
135 Box::<dyn std::error::Error>::from(format!("Failed to create key pair: {e}"))
136 })?;
137 let account = Account::from_key_pair(key_pair.clone(), None, None).map_err(|e| {
138 Box::<dyn std::error::Error>::from(format!(
139 "Failed to create account from key pair: {}",
140 e
141 ))
142 })?;
143
144 Ok(Self { account, mnemonic: mnemonic.to_string() })
145 }
146}
147
148#[cfg(test)]
149mod tests {
150 use super::*;
151
152 #[test]
153 fn test_create_bip39_account() {
154 let password =
155 std::env::var("TEST_PASSWORD").unwrap_or_else(|_| "test_password".to_string());
156 let account =
157 Bip39Account::create(&password).expect("Should be able to create Bip39Account in test");
158
159 assert_eq!(account.mnemonic.split_whitespace().count(), 24);
161
162 assert!(account.account.key_pair().is_some());
164 }
165
166 #[test]
167 fn test_recover_from_mnemonic() {
168 let password =
169 std::env::var("TEST_PASSWORD").unwrap_or_else(|_| "test_password".to_string());
170 let original =
171 Bip39Account::create(&password).expect("Should be able to create Bip39Account in test");
172 let mnemonic = original.mnemonic.clone();
173
174 let recovered = Bip39Account::from_bip39_mnemonic(&password, &mnemonic)
176 .expect("Should be able to recover Bip39Account from mnemonic in test");
177
178 assert_eq!(original.account.get_script_hash(), recovered.account.get_script_hash());
180 assert_eq!(original.mnemonic, recovered.mnemonic);
181 }
182
183 #[test]
184 fn test_invalid_mnemonic() {
185 let result = Bip39Account::from_bip39_mnemonic("password", "invalid mnemonic phrase");
186 assert!(result.is_err());
187 }
188
189 #[test]
190 fn test_different_passwords_different_accounts() {
191 let account1 = Bip39Account::create("password1")
192 .expect("Should be able to create Bip39Account in test");
193 let account2 = Bip39Account::create("password2")
194 .expect("Should be able to create Bip39Account in test");
195
196 assert_ne!(account1.account.get_script_hash(), account2.account.get_script_hash());
197 }
198
199 #[test]
200 fn test_generate_and_recover_bip39_account() {
201 let password =
202 std::env::var("TEST_PASSWORD").unwrap_or_else(|_| "test_password".to_string());
203 let account1 =
204 Bip39Account::create(&password).expect("Should be able to create Bip39Account in test");
205 let account2 = Bip39Account::from_bip39_mnemonic(&password, &account1.mnemonic)
206 .expect("Should be able to recover Bip39Account from mnemonic in test");
207
208 assert_eq!(account1.account.get_address(), account2.account.get_address());
209 assert!(account1.account.key_pair().is_some());
210 assert_eq!(account1.account.key_pair(), account2.account.key_pair());
211 assert_eq!(account1.mnemonic, account2.mnemonic);
212 assert!(!account1.mnemonic.is_empty());
213 }
214}