Skip to main content

gm_lib/utils/
account.rs

1use std::{
2    sync::{
3        atomic::{AtomicBool, AtomicUsize, Ordering},
4        Arc, Mutex,
5    },
6    time::{Duration, Instant},
7};
8
9use alloy::{
10    hex,
11    primitives::{address, Address, U256},
12    signers::{
13        k256::{ecdsa::SigningKey, FieldBytes},
14        local::{MnemonicBuilder, PrivateKeySigner},
15        utils::secret_key_to_address,
16    },
17};
18use coins_bip39::{English, Mnemonic};
19use rand::rngs::OsRng;
20use serde::{Deserialize, Serialize};
21
22pub trait AccountUtils {
23    fn store_mnemonic_wallet(phrase: &str, address: Address) -> crate::Result<()>;
24
25    fn store_private_key(private_key: &FieldBytes, address: Address) -> crate::Result<()>;
26
27    fn get_account_list() -> crate::Result<Vec<Address>>;
28
29    fn get_secret(address: &Address) -> crate::Result<Secret>;
30}
31
32pub struct AccountManager;
33
34impl AccountManager {
35    pub fn create_mnemonic_wallet() -> crate::Result<Address> {
36        let phrase = random_mnemonic()?;
37        Self::import_mnemonic_wallet(&phrase)
38    }
39
40    pub fn import_mnemonic_wallet(phrase: &str) -> crate::Result<Address> {
41        let address = get_address_from_mnemonic(phrase)?;
42        Self::store_mnemonic_wallet(phrase, address)?;
43        Ok(address)
44    }
45
46    pub fn import_private_key(private_key: &str) -> crate::Result<Address> {
47        let private_key = hex::decode(private_key)?;
48        let address = PrivateKeySigner::from_slice(&private_key)?.address();
49        Self::store_private_key(FieldBytes::from_slice(private_key.as_slice()), address)?;
50        Ok(address)
51    }
52
53    pub fn load_wallet(address: &Address) -> crate::Result<PrivateKeySigner> {
54        match Self::get_secret(address)? {
55            Secret::Mnemonic(phrase) => get_signer_from_mnemonic(&phrase),
56            Secret::PrivateKey(private_key) => {
57                Ok(PrivateKeySigner::from_slice(private_key.as_ref())?)
58            }
59        }
60    }
61}
62
63impl AccountUtils for AccountManager {
64    fn store_mnemonic_wallet(phrase: &str, address: Address) -> crate::Result<()> {
65        #[cfg(target_os = "macos")]
66        return macos::Macos::store_mnemonic_wallet(phrase, address);
67
68        #[cfg(target_os = "linux")]
69        return linux_insecure::LinuxInsecure::store_mnemonic_wallet(phrase, address);
70    }
71
72    fn store_private_key(private_key: &FieldBytes, address: Address) -> crate::Result<()> {
73        #[cfg(target_os = "macos")]
74        return macos::Macos::store_private_key(private_key, address);
75
76        #[cfg(target_os = "linux")]
77        return linux_insecure::LinuxInsecure::store_private_key(private_key, address);
78    }
79
80    fn get_account_list() -> crate::Result<Vec<Address>> {
81        #[cfg(target_os = "macos")]
82        return macos::Macos::get_account_list();
83
84        #[cfg(target_os = "linux")]
85        return linux_insecure::LinuxInsecure::get_account_list();
86    }
87
88    fn get_secret(address: &Address) -> crate::Result<Secret> {
89        #[cfg(target_os = "macos")]
90        return macos::Macos::get_secret(address);
91
92        #[cfg(target_os = "linux")]
93        return linux_insecure::LinuxInsecure::get_secret(address);
94    }
95}
96
97fn random_mnemonic() -> crate::Result<String> {
98    let mnemonic = Mnemonic::<English>::new_with_count(&mut OsRng, 24)?;
99    Ok(mnemonic.to_phrase())
100}
101
102fn get_signer_from_mnemonic(phrase: &str) -> crate::Result<PrivateKeySigner> {
103    let signer = MnemonicBuilder::<English>::default()
104        .phrase(phrase)
105        .build()?;
106    Ok(signer)
107}
108
109fn get_address_from_mnemonic(phrase: &str) -> crate::Result<Address> {
110    let signer = get_signer_from_mnemonic(phrase)?;
111    Ok(signer.address())
112}
113
114pub fn mine_wallet(
115    mask_a: Address,
116    mask_b: Address,
117    max_dur: Option<Duration>,
118    shutdown_signal: Arc<AtomicBool>,
119) -> crate::Result<(Option<SigningKey>, usize, Duration)> {
120    let address_one = address!("0xffffffffffffffffffffffffffffffffffffffff");
121    let counter = Arc::new(AtomicUsize::new(0));
122    let stop = Arc::new(AtomicBool::new(false));
123    let result = Arc::new(Mutex::new(None));
124    let start = Instant::now();
125
126    rayon::scope(|s| {
127        for _ in 0..rayon::current_num_threads() {
128            let counter = Arc::clone(&counter);
129            let stop = Arc::clone(&stop);
130            let result = Arc::clone(&result);
131            let shutdown_signal = shutdown_signal.clone();
132            s.spawn(move |_| {
133                // first private key is random
134                let key = coins_bip32::prelude::SigningKey::random(&mut OsRng);
135                let mut u = U256::from_be_slice(&key.to_bytes());
136
137                while !stop.load(Ordering::Relaxed) && !shutdown_signal.load(Ordering::Relaxed) {
138                    if let Some(max_dur) = max_dur {
139                        if Instant::now().duration_since(start) > max_dur {
140                            break;
141                        }
142                    }
143
144                    if let Ok(credential) =
145                        SigningKey::from_bytes(FieldBytes::from_slice(&u.to_be_bytes_vec()))
146                    {
147                        let address = secret_key_to_address(&credential);
148                        if address.bit_and(mask_a) == mask_a
149                            && address.bit_xor(address_one).bit_and(mask_b) == mask_b
150                        {
151                            stop.store(true, Ordering::Relaxed);
152                            let mut result = result.lock().unwrap();
153                            *result = Some(credential);
154                        };
155                    } else {
156                        // generate new random key
157                    }
158                    // change private key by one
159                    u += U256::ONE;
160                    counter.fetch_add(1, Ordering::Relaxed);
161                }
162            });
163        }
164    });
165
166    let result = result.lock().unwrap().clone();
167    let counter = counter.load(Ordering::Relaxed);
168    Ok((result, counter, Instant::now().duration_since(start)))
169}
170
171#[derive(Clone, Debug)]
172pub enum Secret {
173    Mnemonic(String),
174    PrivateKey(FieldBytes),
175}
176
177impl Serialize for Secret {
178    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
179    where
180        S: serde::Serializer,
181    {
182        match self {
183            Secret::Mnemonic(mnemonic) => serializer.serialize_str(mnemonic),
184            Secret::PrivateKey(private_key) => {
185                let hex = hex::encode(private_key);
186                serializer.serialize_str(&hex)
187            }
188        }
189    }
190}
191
192impl<'de> Deserialize<'de> for Secret {
193    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
194    where
195        D: serde::Deserializer<'de>,
196    {
197        let s = String::deserialize(deserializer)?;
198        if s.len() == 64 {
199            let mut bytes = [0u8; 32];
200            hex::decode_to_slice(&s, &mut bytes).map_err(serde::de::Error::custom)?;
201            Ok(Secret::PrivateKey(*FieldBytes::from_slice(
202                bytes.as_slice(),
203            )))
204        } else {
205            Ok(Secret::Mnemonic(s))
206        }
207    }
208}
209
210#[cfg(target_os = "macos")]
211mod macos {
212    use core_foundation::{
213        base::{CFCopyDescription, CFGetTypeID, TCFType},
214        data::CFData,
215        date::CFDate,
216        dictionary::CFDictionary,
217        string::CFString,
218    };
219    use security_framework::{
220        item::{ItemClass, ItemSearchOptions, SearchResult},
221        os::macos::keychain::SecKeychain,
222    };
223    use std::collections::HashMap;
224
225    use super::*;
226
227    fn keychain() -> SecKeychain {
228        SecKeychain::default().expect("SecKeychain::default() - accessing default keychain failed")
229    }
230
231    pub struct Macos;
232
233    impl AccountUtils for Macos {
234        fn store_mnemonic_wallet(phrase: &str, address: Address) -> crate::Result<()> {
235            let mnemonic_service = format!("gm:mnemonic:{address}");
236
237            keychain()
238                .add_generic_password(&mnemonic_service, &address.to_string(), phrase.as_bytes())
239                .map_err(|e| crate::Error::AppleSecurityFrameworkError(Box::new(e)))?;
240
241            Ok(())
242        }
243
244        fn store_private_key(private_key: &FieldBytes, address: Address) -> crate::Result<()> {
245            let pk_service = format!("gm:{address}");
246
247            keychain()
248                .add_generic_password(
249                    &pk_service,
250                    &address.to_string(),
251                    hex::encode(private_key).as_bytes(),
252                )
253                .map_err(|e| crate::Error::AppleSecurityFrameworkError(Box::new(e)))?;
254
255            Ok(())
256        }
257
258        fn get_account_list() -> crate::Result<Vec<Address>> {
259            let mut search = ItemSearchOptions::default();
260            search.class(ItemClass::generic_password());
261            // TODO configure this as this search misses some keys if user has more keychain items.
262            search.limit(1000);
263            search.load_attributes(true);
264
265            let mut accounts = vec![];
266
267            if let Ok(result) = search.search() {
268                for item in result {
269                    if let SearchResult::Dict(item) = item {
270                        let item = simplify_dict(&item);
271                        let service = item.get("svce");
272                        if let Some(service) = service {
273                            if service.starts_with("gm") {
274                                let addr_str =
275                                    item.get("acct").expect("must have an account address");
276                                accounts.push(addr_str.parse()?);
277                            }
278                        }
279                    }
280                }
281            }
282
283            Ok(accounts)
284        }
285
286        fn get_secret(address: &Address) -> crate::Result<Secret> {
287            let mnemonic_signer = || {
288                let mnemonic_service = format!("gm:mnemonic:{address}");
289                keychain()
290                    .find_generic_password(&mnemonic_service, &address.to_string())
291                    .map_err(crate::Error::from)
292                    .and_then(|(pswd, _item)| {
293                        String::from_utf8(pswd.to_vec())
294                            .map_err(crate::Error::from)
295                            .map(Secret::Mnemonic)
296                    })
297            };
298            let pk_signer = || {
299                let pk_service = format!("gm:{address}");
300                keychain()
301                    .find_generic_password(&pk_service, &address.to_string())
302                    .map_err(crate::Error::from)
303                    .and_then(|(pswd, _item)| {
304                        let raw_bytes = pswd.to_vec();
305                        let hex_decoded = hex::decode(&raw_bytes);
306                        let pk = hex_decoded.unwrap_or(raw_bytes);
307
308                        SigningKey::from_slice(&pk)
309                            .map_err(crate::Error::from)
310                            .map(|key| Secret::PrivateKey(key.to_bytes()))
311                    })
312            };
313
314            mnemonic_signer().or(pk_signer())
315        }
316    }
317
318    fn simplify_dict(dict: &CFDictionary) -> HashMap<String, String> {
319        unsafe {
320            let mut retmap = HashMap::new();
321            let (keys, values) = dict.get_keys_and_values();
322            for (k, v) in keys.iter().zip(values.iter()) {
323                let keycfstr = CFString::wrap_under_get_rule((*k).cast());
324                let val: String = match CFGetTypeID(*v) {
325                    cfstring if cfstring == CFString::type_id() => {
326                        format!("{}", CFString::wrap_under_get_rule((*v).cast()))
327                    }
328                    cfdata if cfdata == CFData::type_id() => {
329                        let buf = CFData::wrap_under_get_rule((*v).cast());
330                        let mut vec = Vec::new();
331                        vec.extend_from_slice(buf.bytes());
332                        format!("{}", String::from_utf8_lossy(&vec))
333                    }
334                    cfdate if cfdate == CFDate::type_id() => format!(
335                        "{}",
336                        CFString::wrap_under_create_rule(CFCopyDescription(*v))
337                    ),
338                    _ => String::from("unknown"),
339                };
340                retmap.insert(format!("{keycfstr}"), val);
341            }
342            retmap
343        }
344    }
345
346    #[cfg(test)]
347    mod test {
348        use crate::utils::account::AccountUtils;
349
350        #[test]
351        #[ignore]
352        fn see_all_accounts() {
353            let list = super::Macos::get_account_list();
354
355            println!("{list:#?}");
356            panic!();
357        }
358    }
359}
360
361pub mod linux_insecure {
362    use crate::disk::{DiskInterface, FileFormat};
363
364    use super::*;
365
366    pub struct LinuxInsecure;
367
368    impl AccountUtils for LinuxInsecure {
369        fn store_mnemonic_wallet(phrase: &str, address: Address) -> crate::Result<()> {
370            InsecurePrivateKeyStore::load()?.add(address, Secret::Mnemonic(phrase.to_string()))
371        }
372
373        fn store_private_key(private_key: &FieldBytes, address: Address) -> crate::Result<()> {
374            InsecurePrivateKeyStore::load()?.add(address, Secret::PrivateKey(*private_key))
375        }
376
377        fn get_account_list() -> crate::Result<Vec<Address>> {
378            Ok(InsecurePrivateKeyStore::load()?.list())
379        }
380
381        fn get_secret(address: &Address) -> crate::Result<Secret> {
382            InsecurePrivateKeyStore::load()?
383                .find_by_address(address)
384                .ok_or(crate::Error::SecretNotFound(*address))
385        }
386    }
387
388    // TODO remove this once we have implemented a secure store for linux
389    #[derive(Serialize, Deserialize, Debug, Default)]
390    pub struct InsecurePrivateKeyStore {
391        pub keys: Vec<(Address, Secret)>,
392    }
393
394    impl DiskInterface for InsecurePrivateKeyStore {
395        const FILE_NAME: &'static str = "insecure_private_key_store";
396        const FORMAT: FileFormat = FileFormat::TOML;
397    }
398
399    impl InsecurePrivateKeyStore {
400        pub fn add(&mut self, address: Address, key: Secret) -> crate::Result<()> {
401            self.keys.push((address, key));
402            self.save()
403        }
404
405        pub fn find_by_address(&self, address: &Address) -> Option<Secret> {
406            self.keys.iter().find_map(|(stored_address, key)| {
407                if stored_address == address {
408                    Some(key.clone())
409                } else {
410                    None
411                }
412            })
413        }
414
415        pub fn list(self) -> Vec<Address> {
416            self.keys.into_iter().map(|(address, _)| address).collect()
417        }
418    }
419}