rose-squared-sdk 0.1.0

Privacy-preserving encrypted search SDK implementing the SWiSSSE protocol with forward/backward security and volume-hiding, compilable to WebAssembly
Documentation

// examples/sqlite_store.rs

use async_trait::async_trait;
use rose_squared_sdk::{
    crypto::primitives::{EncValue, Tag},
    server::edb::{EncryptedStore, RawEdbEntry},
    vault::PrivacyVault,
    VaultError, VolumeConfig,
};
use rusqlite::{params, Connection, OptionalExtension, Result as RusqliteResult};
use std::error::Error;
use uuid::Uuid;
use std::sync::Mutex;

/// A simple wrapper around a rusqlite connection that implements EncryptedStore.
pub struct SqliteStore {
    conn: Mutex<Connection>,
}

impl SqliteStore {
    /// Creates a new store with an in-memory SQLite database.
    pub fn new() -> RusqliteResult<Self> {
        let conn = Connection::open_in_memory()?;
        conn.execute(
            "CREATE TABLE edb (
                tag BLOB PRIMARY KEY,
                val BLOB NOT NULL
            )",
            [],
        )?;
        Ok(Self { conn: Mutex::new(conn) })
    }
}

#[async_trait(?Send)]
impl EncryptedStore for SqliteStore {
    async fn get(&self, tag: &Tag) -> Result<Option<EncValue>, VaultError> {
        let tag_hex = hex::encode(&tag.0);
        // Silently log to show the blind nature of the store
        // println!(" [DEBUG] Storage search for tag: {}...", &tag_hex[..16]);
        let conn = self.conn.lock().unwrap();
        conn
            .query_row("SELECT val FROM edb WHERE tag = ?1", params![&tag.0], |row| {
                row.get(0).map(|v: Vec<u8>| EncValue(v))
            })
            .optional()
            .map_err(|e: rusqlite::Error| VaultError::StorageError(e.to_string()))
    }

    async fn put(&self, tag: Tag, value: EncValue) -> Result<(), VaultError> {
        let conn = self.conn.lock().unwrap();
        conn
            .execute(
                "INSERT OR REPLACE INTO edb (tag, val) VALUES (?1, ?2)",
                params![&tag.0, &value.0],
            )
            .map(|_| ())
            .map_err(|e: rusqlite::Error| VaultError::StorageError(e.to_string()))
    }

    async fn delete(&self, tag: &Tag) -> Result<(), VaultError> {
        let conn = self.conn.lock().unwrap();
        conn
            .execute("DELETE FROM edb WHERE tag = ?1", params![&tag.0])
            .map(|_| ())
            .map_err(|e: rusqlite::Error| VaultError::StorageError(e.to_string()))
    }

    async fn get_batch(&self, tags: &[Tag]) -> Result<Vec<Option<EncValue>>, VaultError> {
        println!(" [Storage] Batch fetching {} tags (Real + SWiSSSE Padding)...", tags.len());
        for (i, tag) in tags.iter().enumerate().take(3) {
             println!("    -> Target Tag[{}]: {}...", i, hex::encode(&tag.0)[..16].to_string());
        }
        if tags.len() > 3 { println!("    -> ... (and {} more)", tags.len() - 3); }

        let mut out = Vec::with_capacity(tags.len());
        for tag in tags {
            let conn = self.conn.lock().unwrap();
            let res = conn
                .query_row("SELECT val FROM edb WHERE tag = ?1", params![&tag.0], |row| {
                    row.get(0).map(|v: Vec<u8>| EncValue(v))
                })
                .optional()
                .map_err(|e: rusqlite::Error| VaultError::StorageError(e.to_string()))?;
            out.push(res);
        }
        Ok(out)
    }

    async fn atomic_update(
        &self,
        puts: Vec<RawEdbEntry>,
        removes: Vec<Tag>,
    ) -> Result<(), VaultError> {
        let mut conn = self.conn.lock().unwrap();
        let tx = conn
            .transaction()
            .map_err(|e: rusqlite::Error| VaultError::StorageError(e.to_string()))?;

        for tag in removes {
            tx.execute("DELETE FROM edb WHERE tag = ?1", params![&tag.0])
                .map_err(|e: rusqlite::Error| VaultError::StorageError(e.to_string()))?;
        }

        for entry in puts {
            tx.execute(
                "INSERT OR REPLACE INTO edb (tag, val) VALUES (?1, ?2)",
                params![&entry.tag.0, &entry.value.0],
            )
            .map_err(|e: rusqlite::Error| VaultError::StorageError(e.to_string()))?;
        }

        tx.commit()
            .map_err(|e: rusqlite::Error| VaultError::StorageError(e.to_string()))
    }
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
    let store = SqliteStore::new()?;
    let salt = [0u8; 16]; // In a real app, generate this once and store it.
    let mut vault = PrivacyVault::new("password", &salt, VolumeConfig { n_max: 8 })?;

    let doc_id1 = Uuid::new_v4();
    let keywords1 = ["hello", "world"];
    println!("Adding doc {} with keywords: {:?}", doc_id1, keywords1);
    vault.add_document(&keywords1, doc_id1, &store).await?;

    let doc_id2 = Uuid::new_v4();
    let keywords2 = ["hello", "rust"];
    println!("Adding doc {} with keywords: {:?}", doc_id2, keywords2);
    vault.add_document(&keywords2, doc_id2, &store).await?;

    println!("
Searching for 'hello'...");
    let results = vault.search("hello", &store).await?;
    println!("Found {} results:", results.len());
    for doc_id in results {
        println!(" - {}", doc_id);
    }

    println!("
Searching for 'rust'...");
    let results = vault.search("rust", &store).await?;
    println!("Found {} results:", results.len());
    for doc_id in results {
        println!(" - {}", doc_id);
    }

    // Verify that the database only contains encrypted data
    println!("\nVerifying database contents...");
    let conn = store.conn.lock().unwrap();
    let mut stmt = conn.prepare("SELECT COUNT(*) FROM edb")?;
    let count: i64 = stmt.query_row([], |row| row.get(0))?;
    println!("Database contains {} entries.", count);

    let mut stmt = conn.prepare("SELECT tag, val FROM edb LIMIT 5")?;
    let rows = stmt.query_map([], |row| {
        Ok((row.get::<_, Vec<u8>>(0)?, row.get::<_, Vec<u8>>(1)?))
    })?;

    for row in rows {
        let (tag, val) = row?;
        println!(" - Tag (hex): {}, Value (hex): {}", hex::encode(tag), hex::encode(val));
    }
    println!("
As you can see, the database only stores opaque blobs, protecting user privacy.");


    Ok(())
}