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 } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Search as SearchIcon, Database } from "lucide-react";
import { toast } from "sonner";
import { vault } from "@/lib/vault";

export function Search() {
  const [keyword, setKeyword] = useState("");
  const [isSearching, setIsSearching] = useState(false);
  const [results, setResults] = useState<string[]>([]);
  const [hasSearched, setHasSearched] = useState(false);
  const [logs, setLogs] = useState<string[]>([]);

  const handleSearch = async () => {
    if (!keyword) {
      toast.error("Please enter a keyword to search");
      return;
    }
    
    setIsSearching(true);
    setHasSearched(false);
    setLogs(["[WASM] Generating RO(SE)² keyword trapdoor...", "[WASM] Padding search batch via SWiSSSE protocol..."]);
    
    try {
      const stats = await vault.search(keyword);
      setLogs(prev => [
        ...prev, 
        `[WASM] search() completed in ${stats.time_ms.toFixed(2)}ms`, 
        `[SWiSSSE] Vol. Hiding: Found ${stats.real_evaluated} matched ciphertexts.`, 
        `[SWiSSSE] Evaluated and discarded ${stats.dummies_discarded} dummies.`,
        "[WASM] Local Authenticated Decryption successful."
      ]);
      
      setResults(stats.results);
      setHasSearched(true);
      
      toast.success("Query Successful", {
        description: `Retrieved ${stats.results.length} items from Vault.`,
      });
      console.log(`[ROSE-SDK] search(${keyword}) ->`, stats.results);
    } catch (e) {
      toast.error("WASM Search Error", { description: String(e) });
      console.error(e);
      setLogs(prev => [...prev, `[ERROR] ${String(e)}`]);
    } finally {
      setIsSearching(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">Search Vault</h1>
        <p className="text-slate-400">Retrieve documents via encrypted O(1) keyword queries.</p>
      </div>

      <Card className="border-slate-800 shadow-2xl bg-slate-900/60 backdrop-blur-xl text-slate-100 mb-6 relative overflow-hidden">
        <div className="absolute inset-0 bg-gradient-to-br from-purple-500/5 to-pink-500/5 pointer-events-none" />
        <CardHeader className="relative z-10">
          <CardTitle className="flex items-center text-xl text-white">
            <SearchIcon className="mr-2 h-5 w-5 text-purple-400" />
            Query Index
          </CardTitle>
          <CardDescription className="text-slate-400">
            Generates a tamper-proof search token locally. The untrusted server evaluates it blindly.
          </CardDescription>
        </CardHeader>
        <CardContent className="space-y-6 relative z-10">
          <div className="space-y-2">
            <Label htmlFor="keyword" className="text-slate-300">Target Keyword</Label>
            <div className="flex gap-3">
              <Input 
                id="keyword" 
                placeholder="e.g. secret" 
                value={keyword}
                onChange={(e) => setKeyword(e.target.value)}
                className="max-w-md bg-slate-950/50 border-slate-700 text-white placeholder:text-slate-600 focus-visible:ring-purple-500"
                onKeyDown={(e) => e.key === 'Enter' && handleSearch()}
              />
              <Button onClick={handleSearch} disabled={isSearching} variant="secondary" className="bg-purple-600/20 text-purple-300 border border-purple-500/30 hover:bg-purple-600/40 hover:text-purple-100">
                {isSearching ? "Generating Token..." : "Search"}
              </Button>
            </div>
          </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">// SWiSSSE Protocol Log</span>
            {logs.length === 0 ? (
              <span>{'>'} Idle. Awaiting query execution...</span>
            ) : (
              logs.map((log, i) => (
                <span key={i} className={isSearching && i === logs.length - 1 ? "text-purple-500 animate-pulse" : ""}>
                  {'>'} {log}
                </span>
              ))
            )}
          </div>
        </CardContent>
      </Card>

      {hasSearched && (
        <Card className="border-emerald-500/30 shadow-2xl bg-emerald-950/30 backdrop-blur-xl text-slate-100">
          <CardHeader>
            <CardTitle className="flex items-center text-lg text-emerald-400">
              <Database className="mr-2 h-5 w-5" />
              Decrypted Results
            </CardTitle>
          </CardHeader>
          <CardContent>
            {results.length > 0 ? (
              <ul className="space-y-2">
                {results.map((r, i) => (
                  <li key={i} className="px-4 py-3 border border-slate-700/50 rounded-md bg-slate-900/60 flex items-center justify-between shadow-inner">
                    <span className="font-mono text-slate-300">{r}</span>
                    <span className="text-xs font-semibold tracking-wider uppercase bg-emerald-500/20 text-emerald-400 border border-emerald-500/30 px-2 py-1 rounded">Decrypted</span>
                  </li>
                ))}
              </ul>
            ) : (
              <p className="text-slate-400 italic">No documents found matching this encryption search token.</p>
            )}
          </CardContent>
        </Card>
      )}
    </div>
  );
}