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
import { useState } from "react";
import { Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { FilePlus, Dices } from "lucide-react";
import { toast } from "sonner";
import { vault } from "@/lib/vault";

export function Add() {
  const [docId, setDocId] = useState("");
  const [keywords, setKeywords] = useState("");
  const [isSynthesizing, setIsSynthesizing] = useState(false);
  const [logs, setLogs] = useState<string[]>([]);

  const generateId = () => {
    // Generate a valid UUID v4 (simple version)
    const uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
      const r = Math.random() * 16 | 0, v = c === 'x' ? r : (r & 0x3 | 0x8);
      return v.toString(16);
    });
    setDocId(uuid);
  };

  const handleAdd = async () => {
    if (!docId || !keywords) {
      toast.error("Please provide both Document ID and Keywords");
      return;
    }
    
    setIsSynthesizing(true);
    setLogs(["[SWiSSSE] Initializing PrivacyVault...", "[SWiSSSE] Deriving MasterKeySet via Argon2id...", "[SWiSSSE] Preparing AEAD context (AES-256-GCM)..."]);

    try {
      const keywordList = keywords.split(',').map(k => k.trim().toLowerCase());
      const result = await vault.addDocument(docId, keywordList);

      setLogs(prev => [
        ...prev,
        `[SWiSSSE] add_document() completed in ${result.time_ms.toFixed(2)}ms`,
        `[SWiSSSE] Vol. Hiding: Wrote ${result.real_added} real, ${result.dummies_added} dummy entries.`,
        `[SWiSSSE] Encrypted results persisted to IndexedDbStore.`
      ]);

      toast.success("Document Encrypted & Persisted", {
        description: `ID: ${docId}`,
      });
      console.log(`[SWiSSSE] add_document(${docId})`);
      setDocId("");
      setKeywords("");
    } catch (e) {
      toast.error("WASM Vault Error", { description: String(e) });
      console.error(e);
      setLogs(prev => [...prev, `[ERROR] ${String(e)}`]);
    } finally {
      setIsSynthesizing(false);
    }
  };

  return (
    <div className="max-w-4xl mx-auto px-4 py-12">
      <div className="mb-8">
        <h1 className="text-3xl font-bold tracking-tight mb-2 text-white">Add Document</h1>
        <p className="text-slate-400">Encrypt and index your document into the Privacy Vault.</p>
      </div>

      <Card className="border-slate-800 shadow-2xl bg-slate-900/60 backdrop-blur-xl text-slate-100 relative overflow-hidden">
        <div className="absolute inset-0 bg-gradient-to-br from-blue-500/5 to-purple-500/5 pointer-events-none" />
        <CardHeader className="relative z-10">
          <CardTitle className="flex items-center text-xl text-white">
            <FilePlus className="mr-2 h-5 w-5 text-blue-400" />
            Index New Entry
          </CardTitle>
          <CardDescription className="text-slate-400">
            Documents are encrypted entirely client-side. The untrusted server only sees ciphertexts.
          </CardDescription>
        </CardHeader>
        <CardContent className="space-y-6 relative z-10">
          <div className="space-y-2">
            <Label htmlFor="docId" className="text-slate-300">Document ID</Label>
            <div className="flex gap-3">
              <Input 
                id="docId" 
                placeholder="e.g. valid-uuid-here" 
                value={docId}
                onChange={(e) => setDocId(e.target.value)}
                className="max-w-md bg-slate-950/50 border-slate-700 text-white placeholder:text-slate-600 focus-visible:ring-blue-500"
              />
              <Button type="button" onClick={generateId} variant="outline" className="border-slate-700 bg-slate-800 hover:bg-slate-700 hover:text-white text-slate-300">
                <Dices className="mr-2 h-4 w-4" />
                Generate
              </Button>
            </div>
          </div>
          <div className="space-y-2">
            <Label htmlFor="keywords" className="text-slate-300">Keywords (comma-separated)</Label>
            <Input 
              id="keywords" 
              placeholder="e.g. classification, secret, financials" 
              value={keywords}
              onChange={(e) => setKeywords(e.target.value)}
              className="max-w-md bg-slate-950/50 border-slate-700 text-white placeholder:text-slate-600 focus-visible:ring-blue-500"
            />
          </div>
          <div className="bg-slate-950/80 p-4 rounded-lg border border-slate-800 text-sm font-mono text-slate-400 flex flex-col space-y-1 shadow-inner max-h-48 overflow-y-auto">
            <span className="text-emerald-600 dark:text-emerald-400 font-semibold mb-1">// Event Log</span>
            {logs.length === 0 ? (
              <>
                <span>{'>'} SDK: Ready...</span>
                <span>{'>'} SDK: Waiting for document input...</span>
              </>
            ) : (
              logs.map((log, i) => (
                <span key={i} className={isSynthesizing && i === logs.length - 1 ? "text-blue-500 animate-pulse" : ""}>
                  {'>'} {log}
                </span>
              ))
            )}
          </div>
        </CardContent>
        <CardFooter className="relative z-10 border-t border-slate-800/50 bg-slate-900/30 pt-6">
          <Button onClick={handleAdd} disabled={isSynthesizing} className="w-full sm:w-auto bg-blue-600 hover:bg-blue-500 text-white shadow-lg shadow-blue-500/20">
            {isSynthesizing ? "Encrypting..." : "Add to Vault"}
          </Button>
        </CardFooter>
      </Card>
    </div>
  );
}