etopay_sdk/wallet/
kdbx.rs

1use etopay_wallet::bip39::Mnemonic;
2use kdbx_rs::database::Entry;
3use kdbx_rs::errors::FailedUnlock;
4use kdbx_rs::{CompositeKey, Database, Kdbx};
5use log::info;
6use secrecy::{ExposeSecret, SecretString};
7
8/// load mnemonic from kdbx file
9pub fn load_mnemonic(backup: &[u8], password: &SecretString) -> Result<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
23    let mnemonic = Mnemonic::from_phrase(mnemonic, etopay_wallet::bip39::Language::English)?;
24
25    Ok(mnemonic)
26}
27
28/// store mnemonic in kdbx file
29pub fn store_mnemonic(mnemonic: &Mnemonic, password: &SecretString) -> Result<Vec<u8>, KdbxStorageError> {
30    info!("Creating kdbx file from mnemonic");
31
32    let mut database = Database::default();
33    database.set_name("etopay");
34
35    let mut entry = Entry::default();
36    entry.set_title("mnemonic");
37    entry.set_password(mnemonic.phrase());
38    database.add_entry(entry);
39
40    let mut kdbx = Kdbx::from_database(database);
41    kdbx.set_key(CompositeKey::from_password(password.expose_secret()))?;
42
43    let mut buffer = Vec::new();
44
45    kdbx.write(&mut buffer)?;
46
47    Ok(buffer)
48}
49
50/// Wrapper for kdbx storage errors
51#[derive(Debug, thiserror::Error)]
52pub enum KdbxStorageError {
53    /// Kdbx storage errors
54    #[error("KdbxError: {0}")]
55    KdbxError(#[from] kdbx_rs::Error),
56    /// Kdbx open errors
57    #[error("OpenError: {0}")]
58    OpenError(#[from] kdbx_rs::errors::OpenError),
59    /// Kdbx write errors
60    #[error("WriteError: {0}")]
61    WriteError(#[from] kdbx_rs::errors::WriteError),
62    /// Kdbx unlock errors
63    #[error("UnlockError: {0}")]
64    UnlockError(#[from] kdbx_rs::errors::UnlockError),
65    /// Kdbx key generation errors
66    #[error("KeyGenerationError: {0}")]
67    KeyGenerationError(#[from] kdbx_rs::errors::KeyGenerationError),
68    /// Not found errors
69    #[error("Not found: {0}")]
70    NotFound(String),
71
72    /// Error occurred while handling bip39 compliant mnemonics
73    #[error("Bip39 error: {0:?}")]
74    Bip39(#[from] etopay_wallet::bip39::ErrorKind),
75}
76
77impl From<FailedUnlock> for KdbxStorageError {
78    fn from(funlock: FailedUnlock) -> KdbxStorageError {
79        KdbxStorageError::UnlockError(funlock.1)
80    }
81}
82
83#[cfg(test)]
84mod tests {
85    use super::*;
86    use secrecy::SecretString;
87
88    #[test]
89    fn test_store_and_load_mnemonic() {
90        // Arrange
91        let mnemonic = Mnemonic::new(
92            etopay_wallet::bip39::MnemonicType::Words24,
93            etopay_wallet::bip39::Language::English,
94        );
95
96        let password = SecretString::new("password".into());
97
98        // Act
99        let kdbx = store_mnemonic(&mnemonic, &password).unwrap();
100        let mnemonic_recovered = load_mnemonic(&kdbx, &password).unwrap();
101
102        // Assert
103        assert_eq!(mnemonic_recovered.phrase(), mnemonic.phrase());
104    }
105}