1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
// SWiSSSE: System-Wide Security for Searchable Symmetric Encryption.
// (PoPETS 2024 — upgrade layer on top of RO(SE)²)
//
// ── What SWiSSSE adds ─────────────────────────────────────────────────────────
// RO(SE)² achieves forward/backward security but still leaks:
// (a) Volume leakage — the number of results per keyword.
// (b) Access-pattern leakage — which EDB addresses are fetched per query,
// allowing a persistent server to link repeated searches.
//
// SWiSSSE suppresses both by:
// (a) Padding every write batch to a fixed size `N_max`.
// (b) Padding every read batch to `N_max` dummy lookups.
//
// ── Design decisions (locked) ─────────────────────────────────────────────────
// • `N_max` is fixed at vault creation time. It bounds the maximum number
// of distinct documents that can be indexed under any single keyword.
// • Dummy reads: the client generates `N_max - |real_results|` fake tags
// (random bytes) and includes them in the search token. The server fetches
// all tags uniformly; misses on dummy tags are silently ignored.
// • Dummy writes: `padded_put_batch` on the EDB trait fills up to `N_max`.
// • Both dummies are cryptographically indistinguishable from real ones —
// random tags in a tag space of 2^256 are pseudo-random under PRF security.
use RngCore;
use crate;
use crateVaultError;
// ── VolumeConfig ─────────────────────────────────────────────────────────────
/// Parameters that control the SWiSSSE volume-hiding behaviour.
///
/// Set once at vault creation and stored alongside the encrypted state.
/// Changing `n_max` after vault creation requires a full re-index.
// ── Read padding ──────────────────────────────────────────────────────────────
/// Pad a `SearchToken` with dummy (tag, key) pairs up to `n_max`.
///
/// The returned token has exactly `n_max` pairs. The server fetches all
/// of them; missing entries for dummy tags are silently dropped by
/// `SearchProtocol::finalize_search` (they return `None` from the EDB).
///
/// The dummy keys are random — even if a dummy tag accidentally collides
/// with a real entry (probability 2^{-256}), decryption will fail the GCM
/// check and return `Tampered`, which the caller can distinguish and ignore.
/// Fisher-Yates shuffle for the token pairs.