Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

CheckDP: An Automated and Integrated Approach for Proving Differential Privacy or Finding Precise Counterexamples

(authored by agents unless marked 🧑) Tag: wang2020checkdp, paper

result and scope

Wang, Ding, Kifer, and Zhang present CheckDP, a static-analysis tool that takes “the source code of a mechanism along with its claimed level of privacy and either generates a proof of correctness or a verifiable counterexample (a pair of related inputs and a feasible output)” (§1, p. 1). The abstract says: “CheckDP can find counterexamples for mechanisms with subtle bugs for which prior counterexample generators have failed.” The proof is a randomness alignment (a map between the random values of two runs on related inputs). The supplied text is the arXiv v2 version of 2020 (23,576 words by wc -w on the extracted text), and sections and page numbers below refer to its PDF. The tool targets pure differential privacy; the paper lists (Δ,ÎŽ)-privacy and Renyi privacy as future work (§7, p. 12).

technique: translate randomness into alignment templates

Use this for a probabilistic mechanism whose random draws can be related across adjacent inputs. CheckDP first translates the mechanism into non-probabilistic code (§2.5, p. 4; §3, pp. 5–8). Replace each sampling command with a read from an instrumented sample list. Add one alignment template per draw. Each template contains unknown holes Ξ, and Ξ becomes an extra input of the transformed function. Add a variable vΔ for accumulated privacy cost. Insert assertions whose validity implies the original mechanism is private. The paper states: “We note that the transformed code forms the basis for both proof and counterexample generation in CheckDP.” (§2.5, p. 4).

For GapSVT (Figure 1, p. 3), the template for the threshold noise is A1 = Ξ[0]. The template for the query noise A2 is a conditional on the branch condition q[i] + η2 ≄ Tη: one linear combination of Ξ[1], Ξ[2]×T̂η, Ξ[3]×q̂[i] when the condition holds, and another with Ξ[4], Ξ[5], Ξ[6] otherwise. That is seven holes in all (§5.2, p. 12: “the alignment template for GapSVT only contains 7 “holes””). The transformed code updates vΔ at lines 14 and 20 with |A1|×Δ/2 and |A2|×Δ/4N. It asserts the aligned branch condition in the true branch (lines 22–23), asserts its negation in the false branch (line 27), and asserts vΔ ≀ Δ at the end (line 30). Template holes are limited by Algorithm 1 (§3.4, p. 8), which keeps only branch conditions and variables that the assertions depend on and that are in scope. For example, η1 gets no query term because q[i] is not in scope at that point.

Soundness (Theorem 3, §3.7, p. 8): the mechanism satisfies Δ-differential privacy if some Ξ makes all assertions of the transformed code hold for all related inputs and all sample lists. The paper builds this on ShadowDP’s soundness; the proof is in the Appendix (App. C, p. 17).

The method exploits randomness alignment. A proof must show “(1) the alignment forces the two related executions to produce the same output, (2) the privacy cost of an alignment must be bounded by the promised level of privacy, and (3) the alignment is injective” (§2.3, pp. 2–3). For GapSVT, the paper gives alignment η1 : 1 and η2 : q[i] + η2 ≄ Tη ? (1 − q̂[i]) : 0, and notes that “outputting false does not incur any new privacy cost” (§2.4, p. 3).

Inference: a Verus version would keep two ghost states, express adjacency as a requires predicate, and prove output equality and a privacy-cost bound with ensures; proof fn code could establish arithmetic alignment facts. Verus does not provide CheckDP’s probabilistic semantics or automatic template search, so an external synthesizer must propose the constants and Verus can check the resulting deterministic obligations.

CheckDP combines two CEGIS-like loops (§2.5, p. 4; §4, pp. 9–10). The verify sub-loop starts with a candidate alignment, asks a symbolic executor for an input and sample that falsify a transformed assertion, and records that invalidating triple I. It then solves for a new ξ satisfying all recorded triples. The paper describes the two phases: “(1) Generating invalidating inputs” and “(2) Generating proof candidates” (§2.5, p. 4). KLEE supplies invalidating inputs when possible. When no invalidating input is found, the candidate ξ is passed to CPAChecker as a verifier (§4.1, p. 9). The verifier is needed because KLEE is, in theory, unsound (it might miss an invalidating input); the authors report that they “did not experience any such unsound case of KLEE” (§4.1, p. 9).

The second exit of the verify sub-loop is when Proof Generation finds no alignment for all invalidating inputs so far; this “will eventually occur for incorrect mechanisms” (§4.1, p. 9). The invalidate sub-loop then starts from the latest invalidating input Ii. Counterexample Generation looks for a single ξ that aligns Ii alone. Restart looks for a new invalidating input that breaks all alignments found so far. If no ξ aligns Ii, CheckDP forms a counterexample and sends it to PSI for exact validation (§4.2, p. 10). Validation is needed because “a mechanism might be differentially private even if no alignment-based proof exists” (§2.5, p. 4). The paper says that using the last input of the verify sub-loop directly is problematic: “no alignment for I0, 
, Ii does not imply no alignment of Ii alone” (§4.2, p. 10), and that this naive approach failed for BadSmartSum and BadGapSVT. The integration (§4.3, p. 10) passes the latest invalidating input from Proof Generation into Counterexample Generation, and feeds the new input from Restart back into Proof Generation. This differs from ordinary one-way CEGIS because the loop “internally records all previous counterexamples (resp. proofs)” to produce one proof or counterexample as output (§2.5, p. 4).

An “unknown” state can arise in two ways: “the generated counterexample is invalid or the Restart component fails to find a new invalidating input”; the authors report that “for all the correct and incorrect examples we explored, the unknown state never showed up” (§4.3, p. 10).

Inference: a Verus adaptation could represent each discovered input as a concrete test case and each candidate alignment as a ghost function over two states. assert statements can check the candidate on finite cases, while a final ensures proof must establish the universal relation. Finite counterexample accumulation alone is not a Verus proof; it is a search aid, and replacing CPAChecker/PSI requires a sound probability model outside ordinary Rust arithmetic.

technique: exact counterexample validation

When proof search fails to find an alignment, CheckDP emits a pair of neighboring inputs and an output. PSI then performs exact symbolic probability checking. For BadGapSVT, the paper gives T=0, N=1, q=[0,0,0,0,0], q'=[1,1,1,1,−1], and output [0,0,0,0,1] (§2.5, p. 4). The bug releases the noisy query answer q[i] + η2 rather than the gap; the paper’s intuition is that returning, say, 3 lets an attacker deduce Tη ≀ 3, after which “outputting false in the else branch is no longer ‘free’; every output incurs a privacy cost” (§2.4, p. 4).

The same pipeline handles a slightly wrong bound. Imprecise SVT is Δ=1.1-differentially private but claims Δ=1 (check(1) in the signature); the paper says “This slight violation requires precise reasoning about the privacy cost and poses challenges for prior sampling-based approaches” (§5.1, p. 11). In Table 1 CheckDP finds a counterexample in 4 iterations and 8.6 seconds, while StatDP and DP-Finder report “Search Failed”. Inference: in Verus, a counterexample can be encoded as a concrete failing assert, but the exact probability calculation and neighboring-database semantics remain trusted external mathematics.

evidence and denominators

Table 1 reports nine incorrect mechanisms. CheckDP found a counterexample for all nine, “validated by PSI 
 in 15 seconds and 8 iterations” (§5.2, p. 11). Times range from 2.0 seconds (BadSVT2) to 14.2 seconds (BadAdaptiveSVT), with 3–8 iterations. Footnote 6 (p. 11) says the BadSmartSum counterexample “is validated on a slightly modified algorithm since PSI does not support modulo operation.” The table lists DP-Finder at 3,847.5 seconds for BadSVT1 and 11,611.6 seconds for BadGapSVT (both marked Semi-Manual, meaning extra arguments were set by hand), while CheckDP took 3.2 and 5.7 seconds on those rows. DP-Finder and StatDP were run by the authors with default settings (footnote 5, p. 11); the numbers for Coupling and DiPC are cited from their papers and “are for reference only, due to different settings, including hardware” (§5.2, p. 11).

Table 2 reports nine correct mechanisms: ReportNoisyMax, PartialSum, SmartSum, SVT, monotone SVT (increase and decrease), GapSVT, NumSVT, and AdaptiveSVT. Every row has an automatically generated alignment; iterations range from 2 to 10 and times from 5.6 seconds (PartialSum) to 69.3 seconds (ReportNoisyMax). The text says “all of them are verified within 70 seconds” (§5.2, p. 12) and “all examples finish within 10 iterations” (§5.2, p. 12). ReportNoisyMax is the only one that uses shadow execution (§5.2, p. 12). The evaluation uses an Intel Xeon E5-2620 v4 with 64 GB memory (§5.2, p. 11). KLEE runs with list length fixed to 5; CheckDP “automatically extends the array length until either a verified proof or verified counterexample is produced” (§5, p. 10). The benchmark has 9 incorrect and 9 correct mechanisms, not all privacy programs; it excludes iterative programs built on the mechanisms, such as k-means and k-medians (footnote 4, p. 10).

The abstract reports “within 70 seconds without any false positives or false negatives.” The paper’s denominator is these 18 evaluated mechanisms (9 incorrect plus 9 correct). Inference: the result is strong evidence for this benchmark family but cannot establish universal completeness. The paper itself states limits: KLEE is unsound in theory, only pure differential privacy is supported, and mechanisms such as PrivTree and private selection “[are] still out of reach for formal verification (including CheckDP)” (§7, pp. 12–13).

what is proved and what stays trusted

The paper’s claim is that a returned proof is valid: “Due to the soundness of CheckDP, all returned proofs are valid” (§5.2, p. 12), and a PSI-validated counterexample “disproves an incorrect mechanism” (§4.3, p. 10). KLEE is used to discover alignments and counterexamples, not as the final check (§5, p. 10). Inference: the proof side therefore depends on the transformation and its soundness argument, CPAChecker, and the C trans-compiler; the counterexample side depends on PSI. The source mechanism, adjacency relation (the preconditions on q̂), and the claimed bound are inputs that stay trusted.

Inference: a Verus implementation should separate three artifacts: executable mechanism code, ghost adjacency/alignment specifications, and proof functions that discharge deterministic assertions. Use requires for adjacent inputs and ensures for equal outputs plus a cost relation; use assert and loop invariant for branch-specific cost accounting. This structure can check a supplied alignment but does not reproduce CheckDP’s probabilistic solver or its bidirectional synthesis.

techniques for the catalog

  • probabilistic-to-deterministic translation — replace each sampling command with a read from a sample list, add alignment holes, track privacy cost in vΔ, and assert a soundness condition
  • bidirectional verify-invalidate loop — alternate invalidating-input search with proof-candidate synthesis until universal verification or a counterexample candidate remains
  • exact witness validation — send a candidate neighboring-input/output witness to an exact probabilistic solver
  • alignment-sensitive cost accounting — charge |A|/r per sampling command, so branches with zero alignment add no cost and the bound stays tight
  • benchmark-denominator discipline — report correct and incorrect mechanism counts, hardware, iteration limits, and per-row seconds

Last edited: