rosetta_client/
mnemonic.rs

1use crate::crypto::bip39::{Language, Mnemonic};
2use anyhow::Result;
3#[cfg(not(target_family = "wasm"))]
4use std::fs::OpenOptions;
5#[cfg(not(target_family = "wasm"))]
6use std::io::Write;
7use std::path::Path;
8#[cfg(not(target_family = "wasm"))]
9use std::path::PathBuf;
10#[cfg(target_family = "wasm")]
11use wasm_bindgen::{JsCast, UnwrapThrowExt};
12#[cfg(target_family = "wasm")]
13use web_sys::Storage;
14
15/// Generates a mnemonic.
16pub fn generate_mnemonic() -> Result<Mnemonic> {
17    let mut entropy = [0; 32];
18    getrandom::getrandom(&mut entropy)?;
19    let mnemonic = Mnemonic::from_entropy_in(Language::English, &entropy)?;
20    Ok(mnemonic)
21}
22
23/// Mnemonic storage backend.
24///
25/// On most platforms it will be backed by a file. On wasm it will be
26/// backed by local storage.
27pub struct MnemonicStore {
28    #[cfg(not(target_family = "wasm"))]
29    path: PathBuf,
30    #[cfg(target_family = "wasm")]
31    local_storage: Storage,
32}
33
34impl MnemonicStore {
35    /// Generates a new mnemonic and stores it.
36    pub fn generate(&self) -> Result<Mnemonic> {
37        let mnemonic = generate_mnemonic()?;
38        self.set(&mnemonic)?;
39        Ok(mnemonic)
40    }
41
42    /// Gets a mnemonic if there is one or generates a new mnemonic
43    /// if the store is empty.
44    pub fn get_or_generate_mnemonic(&self) -> Result<Mnemonic> {
45        if self.exists() {
46            self.get()
47        } else {
48            self.generate()
49        }
50    }
51}
52
53#[cfg(not(target_family = "wasm"))]
54impl MnemonicStore {
55    /// Creates a new mnemonic store optinally taking a path.
56    pub fn new(path: Option<&Path>) -> Result<Self> {
57        let path = if let Some(path) = path {
58            path.into()
59        } else {
60            dirs_next::config_dir()
61                .ok_or_else(|| anyhow::anyhow!("no config dir found"))?
62                .join("rosetta-wallet")
63                .join("mnemonic")
64        };
65        Ok(Self { path })
66    }
67
68    /// Sets the stored mnemonic.
69    pub fn set(&self, mnemonic: &Mnemonic) -> Result<()> {
70        std::fs::create_dir_all(self.path.parent().unwrap())?;
71        #[cfg(unix)]
72        use std::os::unix::fs::OpenOptionsExt;
73        let mut opts = OpenOptions::new();
74        opts.create(true).write(true).truncate(true);
75        #[cfg(unix)]
76        opts.mode(0o600);
77        let mut f = opts.open(&self.path)?;
78        f.write_all(mnemonic.to_string().as_bytes())?;
79        Ok(())
80    }
81
82    /// Returns the stored mnemonic.
83    pub fn get(&self) -> Result<Mnemonic> {
84        let mnemonic = std::fs::read_to_string(&self.path)?;
85        let mnemonic = Mnemonic::parse_in(Language::English, mnemonic)?;
86        Ok(mnemonic)
87    }
88
89    /// Checks if a mnemonic is stored.
90    pub fn exists(&self) -> bool {
91        self.path.exists()
92    }
93}
94
95#[cfg(target_family = "wasm")]
96impl MnemonicStore {
97    /// Creates a new mnemonic store optinally taking a path.
98    pub fn new(_path: Option<&Path>) -> Result<Self> {
99        let local_storage = web_sys::window()
100            .expect_throw("no window")
101            .local_storage()
102            .expect_throw("failed to get local_storage")
103            .expect_throw("no local storage");
104        Ok(Self { local_storage })
105    }
106
107    /// Sets the stored mnemonic.
108    pub fn set(&self, mnemonic: &Mnemonic) -> Result<()> {
109        self.local_storage
110            .set_item("mnemonic", &mnemonic.to_string())
111            .map_err(|value| {
112                anyhow::anyhow!(String::from(
113                    value.dyn_into::<js_sys::Error>().unwrap().to_string()
114                ))
115            })?;
116        Ok(())
117    }
118
119    /// Returns the stored mnemonic.
120    pub fn get(&self) -> Result<Mnemonic> {
121        let mnemonic = self
122            .local_storage
123            .get_item("mnemonic")
124            .expect_throw("unreachable: get_item does not throw an exception")
125            .expect_throw("no mnemonic in store");
126        Ok(Mnemonic::parse_in(Language::English, &mnemonic)?)
127    }
128
129    /// Checks if a mnemonic is stored.
130    pub fn exists(&self) -> bool {
131        self.local_storage
132            .get_item("mnemonic")
133            .expect_throw("unreachable: get_item does not throw an exception")
134            .is_some()
135    }
136}