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
// Foundation types for the RO(SE)² + SWiSSSE protocol.
// Every byte that crosses a trust boundary goes through these types.
// All secret values implement `Zeroize` so they are wiped from memory on drop.

use zeroize::{Zeroize, ZeroizeOnDrop};
use serde::{Deserialize, Serialize};

// ── Security parameter ─────────────────────────────────────────────────────────
/// λ = 256 bits.  All PRF outputs, keys, and tags are this length.
pub const LAMBDA: usize = 32;

// ── Secret key types ───────────────────────────────────────────────────────────

/// A 256-bit secret key.  Wiped from memory the moment it goes out of scope.
///
/// Never clone, print, or log this value.
#[derive(Zeroize, ZeroizeOnDrop)]
pub struct SecretKey(pub [u8; LAMBDA]);

impl SecretKey {
    /// Create from raw bytes (e.g., from HKDF output).
    pub fn from_bytes(bytes: [u8; LAMBDA]) -> Self {
        Self(bytes)
    }

    /// View the raw bytes for use in HMAC/HKDF — no copy.
    pub fn as_bytes(&self) -> &[u8; LAMBDA] {
        &self.0
    }
}

// ── EDB address tag ────────────────────────────────────────────────────────────

/// A pseudorandom tag that acts as the *address* of one entry in the server EDB.
///
/// Derived as:  tag = PRF(K_tag, "tag:" || keyword || index || epoch)
///
/// To the server this is indistinguishable from random bytes — it reveals
/// nothing about the keyword, document, or time of write.
#[derive(Clone, PartialEq, Eq, Hash, Debug, Serialize, Deserialize)]
pub struct Tag(pub [u8; LAMBDA]);

// ── Encrypted value ────────────────────────────────────────────────────────────

/// An AES-256-GCM ciphertext stored at a Tag address on the server.
/// Contains:  nonce (12 B) || ciphertext || GCM tag (16 B)
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct EncValue(pub Vec<u8>);

// ── Entry operation ────────────────────────────────────────────────────────────

/// What an EDB entry represents after decryption.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum EntryOp {
    /// This keyword indexes this document — a live "add" record.
    Add,
}

/// The plaintext payload encrypted inside each `EncValue`.
/// After decryption the client learns the doc_id this entry refers to.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct EntryPayload {
    pub doc_id:    uuid::Uuid,
    pub op:        EntryOp,
    /// Milliseconds since UNIX epoch — used for result ordering, never sent in clear.
    pub timestamp: u64,
}

// ── Search token ───────────────────────────────────────────────────────────────

/// A SearchToken authorises exactly one search query.
///
/// It contains a list of (EDB address, decryption key) pairs — one per live
/// result.  The server fetches each Tag; the client decrypts each value.
///
/// **Forward security**: a token generated at time T cannot read entries
/// written after T (they have different tags from fresh indices).
///
/// **Backward security**: a token generated after a deletion cannot read
/// the deleted entry (its tag was retired with the old epoch).
pub struct SearchToken {
    /// One pair per live entry for this keyword.
    pub pairs: Vec<(Tag, [u8; LAMBDA])>,
}

// ── Epoch ──────────────────────────────────────────────────────────────────────

/// A deletion-epoch counter per keyword.
///
/// Every time a document is deleted from a keyword's result set the epoch
/// increments.  All EDB tags for that keyword are re-derived under the new
/// epoch, so old server entries become permanently unreachable — this gives
/// **Backward Security Type-II** as defined in the RO(SE)² paper.
pub type Epoch = u64;