iop_vault/
vault.rs

1use super::*;
2
3#[cfg_attr(target_arch = "wasm32", typetag::serialize(tag = "pluginName"))]
4#[cfg_attr(not(target_arch = "wasm32"), typetag::serde(tag = "pluginName"))]
5pub trait VaultPlugin: Send + Sync {
6    fn name(&self) -> &'static str;
7    fn to_any(&self) -> Box<dyn Any>;
8    fn eq(&self, other: &dyn VaultPlugin) -> bool;
9}
10
11impl fmt::Debug for dyn VaultPlugin {
12    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
13        formatter.write_str(self.name())
14    }
15}
16
17#[cfg_attr(not(target_arch = "wasm32"), derive(Deserialize))]
18#[derive(Debug, Serialize)]
19#[serde(rename_all = "camelCase")]
20struct VaultImpl {
21    encrypted_seed: String,
22    plugins: Vec<Box<dyn VaultPlugin>>,
23    #[serde(skip)]
24    is_dirty: bool, // Plugins and language bindings are trusted to use this properly
25}
26
27impl VaultImpl {
28    fn new(encrypted_seed: String, plugins: Vec<Box<dyn VaultPlugin>>, is_dirty: bool) -> Self {
29        Self { encrypted_seed, plugins, is_dirty }
30    }
31}
32
33#[cfg_attr(not(target_arch = "wasm32"), derive(Deserialize))]
34#[derive(Clone, Debug, Serialize)]
35#[serde(transparent)]
36pub struct Vault {
37    inner: Arc<RwLock<VaultImpl>>,
38}
39
40impl Vault {
41    pub fn new(encrypted_seed: String, plugins: Vec<Box<dyn VaultPlugin>>, dirty: bool) -> Self {
42        let imp = VaultImpl::new(encrypted_seed, plugins, dirty);
43        let inner = Arc::new(RwLock::new(imp));
44        Self { inner }
45    }
46
47    pub fn create(
48        lang_code: Option<&str>, phrase: impl AsRef<str>, bip39_password: impl AsRef<str>,
49        unlock_password: impl AsRef<str>,
50    ) -> Result<Vault> {
51        let bip39 = match lang_code {
52            None => Bip39::new(),
53            Some(code) => Bip39::language_code(code)?,
54        };
55        let seed = bip39.phrase(phrase)?.password(bip39_password);
56        let encrypted_seed = Self::encrypt_seed(&seed, unlock_password.as_ref())?;
57        let vault = Self::new(encrypted_seed, Vec::new(), true);
58        Ok(vault)
59    }
60
61    pub fn unlock(&self, unlock_password: &str) -> Result<Seed> {
62        let imp = self.inner.try_read().ok_or_else(|| format_err!("Read lock on Vault failed"))?;
63        Self::decrypt_seed(&imp.encrypted_seed, unlock_password)
64    }
65
66    pub fn plugins_by_type<T: VaultPlugin + 'static>(&self) -> Result<Vec<Box<T>>> {
67        let imp = self.inner.try_read().ok_or_else(|| format_err!("Read lock on Vault failed"))?;
68        let plugins =
69            imp.plugins.iter().by_ref().filter_map(|p| p.to_any().downcast().ok()).collect();
70        Ok(plugins)
71    }
72
73    pub fn add(&mut self, plugin: Box<dyn VaultPlugin>) -> Result<()> {
74        let mut imp =
75            self.inner.try_write().ok_or_else(|| format_err!("Write lock on Vault failed"))?;
76        ensure!(
77            imp.plugins.iter().all(|p| !p.eq(plugin.as_ref())),
78            "Same plugin was already added to vault"
79        );
80        imp.plugins.push(plugin);
81        imp.is_dirty = true;
82        Ok(())
83    }
84
85    pub fn to_modifiable(&self) -> Box<dyn State<bool>> {
86        <dyn State<_>>::map(&self.inner, |v| &v.is_dirty, |v| &mut v.is_dirty)
87    }
88
89    fn encrypt_seed(seed: &Seed, unlock_password: &str) -> Result<String> {
90        let nonce = nonce()?;
91        let encrypted_seed_bytes = encrypt(seed.as_bytes(), unlock_password, nonce)?;
92        Ok(multibase::encode(multibase::Base::Base64Url, &encrypted_seed_bytes))
93    }
94
95    fn decrypt_seed(seed: &str, unlock_password: &str) -> Result<Seed> {
96        let (_, encrypted_seed_bytes) = multibase::decode(seed)?;
97        let decrypted_bytes = decrypt(&encrypted_seed_bytes, unlock_password)?;
98        Seed::from_bytes(&decrypted_bytes)
99    }
100}
101
102pub trait PluginPublic<T: VaultPlugin>: Sized {
103    fn create(plugin: &T, vault_dirty: Box<dyn State<bool>>) -> Result<Self>;
104}
105
106pub trait PluginPrivate<T: VaultPlugin>: Sized {
107    fn create(plugin: &T, seed: Seed, vault_dirty: Box<dyn State<bool>>) -> Result<Self>;
108}
109
110pub struct BoundPlugin<T: VaultPlugin, TPublic: PluginPublic<T>, TPriv: PluginPrivate<T>> {
111    vault: Vault,
112    plugin: T,
113    _pub: PhantomData<TPublic>,
114    _priv: PhantomData<TPriv>,
115}
116
117impl<T: VaultPlugin, TPublic: PluginPublic<T>, TPriv: PluginPrivate<T>>
118    BoundPlugin<T, TPublic, TPriv>
119{
120    pub fn new(vault: Vault, plugin: T) -> Self {
121        Self { vault, plugin, _pub: Default::default(), _priv: Default::default() }
122    }
123
124    pub fn private(&self, unlock_password: impl AsRef<str>) -> Result<TPriv> {
125        let seed = self.vault.unlock(unlock_password.as_ref())?;
126        TPriv::create(&self.plugin, seed, self.vault.to_modifiable())
127    }
128
129    pub fn public(&self) -> Result<TPublic> {
130        TPublic::create(&self.plugin, self.vault.to_modifiable())
131    }
132}