1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
use crate::crypto::bip39::{Language, Mnemonic};
use anyhow::Result;
#[cfg(not(target_family = "wasm"))]
use std::fs::OpenOptions;
#[cfg(not(target_family = "wasm"))]
use std::io::Write;
use std::path::Path;
#[cfg(not(target_family = "wasm"))]
use std::path::PathBuf;
#[cfg(target_family = "wasm")]
use wasm_bindgen::{JsCast, UnwrapThrowExt};
#[cfg(target_family = "wasm")]
use web_sys::Storage;

/// Generates a mnemonic.
pub fn generate_mnemonic() -> Result<Mnemonic> {
    let mut entropy = [0; 32];
    getrandom::getrandom(&mut entropy)?;
    let mnemonic = Mnemonic::from_entropy_in(Language::English, &entropy)?;
    Ok(mnemonic)
}

/// Mnemonic storage backend.
///
/// On most platforms it will be backed by a file. On wasm it will be
/// backed by local storage.
pub struct MnemonicStore {
    #[cfg(not(target_family = "wasm"))]
    path: PathBuf,
    #[cfg(target_family = "wasm")]
    local_storage: Storage,
}

impl MnemonicStore {
    /// Generates a new mnemonic and stores it.
    pub fn generate(&self) -> Result<Mnemonic> {
        let mnemonic = generate_mnemonic()?;
        self.set(&mnemonic)?;
        Ok(mnemonic)
    }

    /// Gets a mnemonic if there is one or generates a new mnemonic
    /// if the store is empty.
    pub fn get_or_generate_mnemonic(&self) -> Result<Mnemonic> {
        if self.exists() {
            self.get()
        } else {
            self.generate()
        }
    }
}

#[cfg(not(target_family = "wasm"))]
impl MnemonicStore {
    /// Creates a new mnemonic store optinally taking a path.
    pub fn new(path: Option<&Path>) -> Result<Self> {
        let path = if let Some(path) = path {
            path.into()
        } else {
            dirs_next::config_dir()
                .ok_or_else(|| anyhow::anyhow!("no config dir found"))?
                .join("rosetta-wallet")
                .join("mnemonic")
        };
        Ok(Self { path })
    }

    /// Sets the stored mnemonic.
    pub fn set(&self, mnemonic: &Mnemonic) -> Result<()> {
        std::fs::create_dir_all(self.path.parent().unwrap())?;
        #[cfg(unix)]
        use std::os::unix::fs::OpenOptionsExt;
        let mut opts = OpenOptions::new();
        opts.create(true).write(true).truncate(true);
        #[cfg(unix)]
        opts.mode(0o600);
        let mut f = opts.open(&self.path)?;
        f.write_all(mnemonic.to_string().as_bytes())?;
        Ok(())
    }

    /// Returns the stored mnemonic.
    pub fn get(&self) -> Result<Mnemonic> {
        let mnemonic = std::fs::read_to_string(&self.path)?;
        let mnemonic = Mnemonic::parse_in(Language::English, mnemonic)?;
        Ok(mnemonic)
    }

    /// Checks if a mnemonic is stored.
    pub fn exists(&self) -> bool {
        self.path.exists()
    }
}

#[cfg(target_family = "wasm")]
impl MnemonicStore {
    /// Creates a new mnemonic store optinally taking a path.
    pub fn new(_path: Option<&Path>) -> Result<Self> {
        let local_storage = web_sys::window()
            .expect_throw("no window")
            .local_storage()
            .expect_throw("failed to get local_storage")
            .expect_throw("no local storage");
        Ok(Self { local_storage })
    }

    /// Sets the stored mnemonic.
    pub fn set(&self, mnemonic: &Mnemonic) -> Result<()> {
        self.local_storage
            .set_item("mnemonic", &mnemonic.to_string())
            .map_err(|value| {
                anyhow::anyhow!(String::from(
                    value.dyn_into::<js_sys::Error>().unwrap().to_string()
                ))
            })?;
        Ok(())
    }

    /// Returns the stored mnemonic.
    pub fn get(&self) -> Result<Mnemonic> {
        let mnemonic = self
            .local_storage
            .get_item("mnemonic")
            .expect_throw("unreachable: get_item does not throw an exception")
            .expect_throw("no mnemonic in store");
        Ok(Mnemonic::parse_in(Language::English, &mnemonic)?)
    }

    /// Checks if a mnemonic is stored.
    pub fn exists(&self) -> bool {
        self.local_storage
            .get_item("mnemonic")
            .expect_throw("unreachable: get_item does not throw an exception")
            .is_some()
    }
}