1use crate::error::CoreError;
7use crate::normalize_mnemonic;
8use bip39::Mnemonic;
9use ed25519_dalek::SigningKey;
10use multiversx_sdk::wallet;
11use std::convert::TryInto;
12
13fn parse_mnemonic(mnemonic_str: &str) -> Result<Mnemonic, CoreError> {
17 let mnemonic_phrase = normalize_mnemonic(mnemonic_str);
18 Mnemonic::parse(&mnemonic_phrase).map_err(|e| CoreError::InvalidMnemonic(e.to_string()))
19}
20
21pub fn derive_signing_key(
41 mnemonic_str: &str,
42 account: u32,
43 index: u32,
44) -> Result<SigningKey, CoreError> {
45 let mnemonic = parse_mnemonic(mnemonic_str)?;
46
47 let private_key = wallet::Wallet::get_private_key_from_mnemonic(mnemonic, account, index);
49
50 let private_key_hex = private_key.to_string();
52 let seed_bytes = hex::decode(&private_key_hex)
53 .map_err(|e| CoreError::InvalidPrivateKey(format!("invalid private key hex: {e}")))?;
54
55 let seed: [u8; 32] = seed_bytes.as_slice().try_into().map_err(|_| {
57 CoreError::InvalidPrivateKey(format!("expected 32-byte seed, got {}", seed_bytes.len()))
58 })?;
59
60 Ok(SigningKey::from_bytes(&seed))
61}
62
63pub fn derive_address(mnemonic_str: &str, account: u32, index: u32) -> Result<String, CoreError> {
84 let mnemonic = parse_mnemonic(mnemonic_str)?;
85
86 let private_key = wallet::Wallet::get_private_key_from_mnemonic(mnemonic, account, index);
88
89 let private_key_hex = private_key.to_string();
91 let wallet_obj = wallet::Wallet::from_private_key(&private_key_hex)
92 .map_err(|e| CoreError::WalletCreation(e.to_string()))?;
93
94 Ok(wallet_obj.to_address().to_bech32_default().to_string())
95}
96
97#[cfg(test)]
98mod tests {
99 use super::*;
100
101 const TEST_MNEMONIC: &str = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
103
104 #[test]
105 fn test_derive_signing_key_success() {
106 let result = derive_signing_key(TEST_MNEMONIC, 0, 0);
107 assert!(result.is_ok());
108
109 let key1 = derive_signing_key(TEST_MNEMONIC, 0, 0).unwrap();
111 let key2 = derive_signing_key(TEST_MNEMONIC, 0, 0).unwrap();
112 assert_eq!(key1.to_bytes(), key2.to_bytes());
113 }
114
115 #[test]
116 fn test_derive_signing_key_invalid_mnemonic() {
117 let result = derive_signing_key("invalid mnemonic words that dont exist", 0, 0);
118 assert!(result.is_err());
119 assert!(result.unwrap_err().to_string().contains("invalid mnemonic"));
120 }
121
122 #[test]
123 fn test_derive_signing_key_different_indices() {
124 let key0 = derive_signing_key(TEST_MNEMONIC, 0, 0).unwrap();
125 let key1 = derive_signing_key(TEST_MNEMONIC, 0, 1).unwrap();
126 let key2 = derive_signing_key(TEST_MNEMONIC, 1, 0).unwrap();
127
128 assert_ne!(key0.to_bytes(), key1.to_bytes());
130 assert_ne!(key0.to_bytes(), key2.to_bytes());
131 assert_ne!(key1.to_bytes(), key2.to_bytes());
132 }
133
134 #[test]
135 fn test_derive_address_success() {
136 let result = derive_address(TEST_MNEMONIC, 0, 0);
137 assert!(result.is_ok());
138
139 let address = result.unwrap();
140 assert!(address.starts_with("erd1"));
141 assert_eq!(address.len(), 62); }
143
144 #[test]
145 fn test_derive_address_invalid_mnemonic() {
146 let result = derive_address("invalid mnemonic words that dont exist", 0, 0);
147 assert!(result.is_err());
148 assert!(result.unwrap_err().to_string().contains("invalid mnemonic"));
149 }
150
151 #[test]
152 fn test_derive_address_different_indices() {
153 let addr0 = derive_address(TEST_MNEMONIC, 0, 0).unwrap();
154 let addr1 = derive_address(TEST_MNEMONIC, 0, 1).unwrap();
155 let addr2 = derive_address(TEST_MNEMONIC, 1, 0).unwrap();
156
157 assert_ne!(addr0, addr1);
159 assert_ne!(addr0, addr2);
160 assert_ne!(addr1, addr2);
161 }
162
163 #[test]
164 fn test_derive_address_deterministic() {
165 let addr1 = derive_address(TEST_MNEMONIC, 0, 0).unwrap();
166 let addr2 = derive_address(TEST_MNEMONIC, 0, 0).unwrap();
167 assert_eq!(addr1, addr2);
168 }
169
170 #[test]
171 fn test_normalize_mnemonic_in_derivation() {
172 let mnemonic_commas = "abandon,abandon,abandon,abandon,abandon,abandon,abandon,abandon,abandon,abandon,abandon,about";
174 let mnemonic_spaces = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
175 let mnemonic_normal = TEST_MNEMONIC;
176
177 let addr_commas = derive_address(mnemonic_commas, 0, 0).unwrap();
178 let addr_spaces = derive_address(mnemonic_spaces, 0, 0).unwrap();
179 let addr_normal = derive_address(mnemonic_normal, 0, 0).unwrap();
180
181 assert_eq!(addr_commas, addr_normal);
183 assert_eq!(addr_spaces, addr_normal);
184 }
185
186 #[test]
187 fn test_signing_key_and_address_match() {
188 let signing_key = derive_signing_key(TEST_MNEMONIC, 0, 0).unwrap();
190 let address = derive_address(TEST_MNEMONIC, 0, 0).unwrap();
191
192 let verifying_key = signing_key.verifying_key();
194 let pubkey_bytes = verifying_key.to_bytes();
195
196 let (_hrp, addr_bytes) = bech32::decode(&address).unwrap();
198 let addr_bytes_array: [u8; 32] = addr_bytes.as_slice().try_into().unwrap();
199
200 assert_eq!(pubkey_bytes, addr_bytes_array);
202 }
203}