rosetta_client/
mnemonic.rs1use 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
15pub 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
23pub 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 pub fn generate(&self) -> Result<Mnemonic> {
37 let mnemonic = generate_mnemonic()?;
38 self.set(&mnemonic)?;
39 Ok(mnemonic)
40 }
41
42 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 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 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 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 pub fn exists(&self) -> bool {
91 self.path.exists()
92 }
93}
94
95#[cfg(target_family = "wasm")]
96impl MnemonicStore {
97 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 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 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 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}