etopay_sdk/wallet/
kdbx.rs

1use iota_sdk::crypto::keys::bip39::Mnemonic;
2use kdbx_rs::database::Entry;
3use kdbx_rs::errors::FailedUnlock;
4use kdbx_rs::{CompositeKey, Database, Kdbx};
5use log::info;
6use secrecy::{ExposeSecret, SecretBox, SecretString};
7
8/// load mnemonic from kdbx file
9pub fn load_mnemonic(backup: &[u8], password: &SecretString) -> Result<SecretBox<Mnemonic>, KdbxStorageError> {
10    info!("Loading kdbx file from bytes");
11    let kdbx = kdbx_rs::from_reader(backup)?;
12    let key = CompositeKey::from_password(password.expose_secret());
13    let unlocked = kdbx.unlock(&key)?;
14
15    let Some(entry) = unlocked.find_entry(|entry| entry.title() == Some("mnemonic")) else {
16        return Err(KdbxStorageError::NotFound("Entry not found".to_string()));
17    };
18
19    let Some(mnemonic) = entry.password() else {
20        return Err(KdbxStorageError::NotFound("Mnemonic not found".to_string()));
21    };
22    let mnemonic = Mnemonic::from(mnemonic);
23    Ok(SecretBox::new(Box::new(mnemonic)))
24}
25
26/// store mnemonic in kdbx file
27pub fn store_mnemonic(mnemonic: &SecretBox<Mnemonic>, password: &SecretString) -> Result<Vec<u8>, KdbxStorageError> {
28    info!("Creating kdbx file from mnemonic");
29
30    let mut database = Database::default();
31    database.set_name("etopay");
32
33    let mut entry = Entry::default();
34    entry.set_title("mnemonic");
35    entry.set_password(mnemonic.expose_secret().to_string());
36    database.add_entry(entry);
37
38    let mut kdbx = Kdbx::from_database(database);
39    kdbx.set_key(CompositeKey::from_password(password.expose_secret()))?;
40
41    let mut buffer = Vec::new();
42
43    kdbx.write(&mut buffer)?;
44
45    Ok(buffer)
46}
47
48/// Wrapper for kdbx storage errors
49#[derive(Debug, thiserror::Error)]
50pub enum KdbxStorageError {
51    /// Kdbx storage errors
52    #[error("KdbxError: {0}")]
53    KdbxError(#[from] kdbx_rs::Error),
54    /// Kdbx open errors
55    #[error("OpenError: {0}")]
56    OpenError(#[from] kdbx_rs::errors::OpenError),
57    /// Kdbx write errors
58    #[error("WriteError: {0}")]
59    WriteError(#[from] kdbx_rs::errors::WriteError),
60    /// Kdbx unlock errors
61    #[error("UnlockError: {0}")]
62    UnlockError(#[from] kdbx_rs::errors::UnlockError),
63    /// Kdbx key generation errors
64    #[error("KeyGenerationError: {0}")]
65    KeyGenerationError(#[from] kdbx_rs::errors::KeyGenerationError),
66    /// Not found errors
67    #[error("Not found: {0}")]
68    NotFound(String),
69}
70
71impl From<FailedUnlock> for KdbxStorageError {
72    fn from(funlock: FailedUnlock) -> KdbxStorageError {
73        KdbxStorageError::UnlockError(funlock.1)
74    }
75}
76
77#[cfg(test)]
78mod tests {
79    use super::*;
80    use secrecy::SecretString;
81
82    #[test]
83    fn test_store_and_load_mnemonic() {
84        // Arrange
85        let mnemonic = SecretBox::new(Box::new("secret mnemonic".into()));
86        let password = SecretString::new("password".into());
87
88        // Act
89        let kdbx = store_mnemonic(&mnemonic, &password).unwrap();
90        let mnemonic = load_mnemonic(&kdbx, &password).unwrap();
91
92        // Assert
93        assert_eq!(mnemonic.expose_secret().to_string(), "secret mnemonic");
94    }
95}