# Zero Knowledge

> How Hekate's zero-knowledge proofs hide the witness: one-time pads on rounds, claims and bus sums, a hiding hash-based commitment, a zk-Ligero outer argument.

- URL: https://oumuamua.dev/hekate/docs/advanced/zero-knowledge
- Published: 2026-10-04T13:50:26+07:00
- Updated: 2026-10-07T13:34:41+07:00

A Hekate proof made with zero knowledge on, the default, convinces the verifier that every table satisfies the program
and reveals nothing else about the rows. The hiding happens in three places: the commitment opens only columns that look
random, a one-time pad covers every round, claim and bus sum the proof sends, and the checks the verifier cannot run on padded numbers go
into an outer statement, which a zk-Ligero argument proves at the end. This page specifies all three, down to the gadget
that keeps packed bit columns working under the pads. [PIOP Protocol](https://oumuamua.dev/hekate/docs/advanced/piop-protocol) covers the
protocol they hide.

## What zero knowledge promises

A proof is zero-knowledge when a simulator that never sees the witness produces proofs nobody can tell from real ones.
Whatever a verifier learns from a real proof, it could have computed from the statement alone.

In Hekate the statement is public: the program, its public inputs, the `Config` and the height of every table. The
witness is the trace, every cell the prover filled. Zero knowledge hides the cells and leaves the statement in view, and
a verifier still sees how many rows each table has.

The guarantee is computational. The pads and every other random value are derived from the 32-byte seed you pass to
`prove`, and telling a simulated proof from a real one takes breaking those generators or the hash.
[Why the proof is zero-knowledge](https://oumuamua.dev/hekate/docs/advanced/zero-knowledge#why-the-proof-is-zero-knowledge) gives the argument.

## What a proof in the clear gives away

A proof made with `zero_knowledge: false` states the value of every column at $r_{\text{final}}$, and each value is a
weighted sum of the column's rows with weights anyone can compute from the point.

Take the four-row column of [PIOP Protocol](https://oumuamua.dev/hekate/docs/advanced/piop-protocol#a-column-is-a-polynomial), in ordinary
integers, at the point (2, 3), where rows 0 to 3 weigh 2, $-4$, $-3$ and 6. Rows 0, 1 and 3 hold the public values 3, 5
and 6, row 2 holds a secret $s$, and the proof claims 16:

$$
16 = 2 \cdot 3 - 4 \cdot 5 - 3s + 6 \cdot 6 = 22 - 3s,
\qquad
s = 2.
$$

In GF(2^128) the solve is the same: one field division. Each claim is one linear equation in the rows of its column.
When most rows are unknown, one equation pins down none of them, and when a column holds one secret among public values,
the equation has one unknown. Zero knowledge has to hold for every witness, the sparse ones included.

The claims are one channel of several: every sumcheck round polynomial, the row fold $q$ and the values at $r'$ are
sums over the rows as well.

## Where the proof hides the trace

Every value a proof carries is public by design or hidden by one of a few devices, and the table accounts for each.

| The proof carries                                                    | Hidden by                                    |
|:---------------------------------------------------------------------|:---------------------------------------------|
| One Merkle root over all tables, and the helper and outer roots      | The hash, over leaves that look random       |
| Opened columns of every table                                        | Random support in every encoded row          |
| The row fold $q$ and the committed values at $r'$                    | One random column in each batch              |
| Sumcheck rounds, column claims, bus sums and helper values           | A one-time pad                               |
| Opened columns of the outer argument                                 | $t + 1$ random symbols in every row          |
| The three responses of the outer argument                            | A mask over the whole space each test allows |
| The point $r_{\text{final}}$                                         | Nothing: a hash of the transcript            |
| The last claim of each sumcheck                                      | Nothing: the masked rounds fix it            |

[`wire_census`][census], a test in the Hekate repository, walks real proofs, from a single table without buses to
programs with chiplets and packed bits, and assigns every field element they publish to one row of this table. It
destructures the proof format field by field, Brakedown openings aside, and a new field breaks its build until someone
classifies it.

## The commitment

The commitment hides its openings with random symbols inside every encoded row, and its folds with one random column in
each batch.

### Opened columns

Every encoded row of a committed table mixes in a block of random symbols, and any `num_queries` opened positions of a
row are uniformly random.

[Prover Engine](https://oumuamua.dev/hekate/docs/basics/prover-engine#step-1-commit) draws the commitment: the tables as one grid, each row
encoded with a Reed-Solomon code, a Merkle tree over the encoded columns. A row's message holds the trace values and a
support of more than `num_queries` symbols at coordinates of its own, and the support is random.
The encoding reads the message as the coefficients of one polynomial and stores its values at the points of an
evaluation domain, computed with the additive FFT of the binary field. No position of the codeword copies a trace
value.

![One row of a committed table: a support of three random symbols, outlined, and the trace values 3, 5, 2, 6 and 1, read as the coefficients of one polynomial and evaluated at 16 points. Every position of the encoded row mixes in the support, and two opened positions are outlined. Any num_queries opened positions are uniform, and the evaluation at r′ reads the trace values alone.](https://oumuamua.dev/assets/hekate/docs/advanced/zero-knowledge/support.b24fe3f8db031a9b579728e7becb94c796be25b503f101db7319ea42bdf202f0.svg "Random support in every row. Sizes are illustrative.")

The support sits in the low coefficients, and its share of any `num_queries` positions of the codeword can take every
value, which makes the opened positions uniform whatever the trace holds. Under a nonzero security floor, the security
check rejects a grid whose support does not exceed `num_queries`. The evaluation at $r'$ reads only the trace part of $q$, and the support,
committed in full with the data, changes the codewords and never the claim. The construction follows the randomizable
support of the Binius64 specification (sec. 2.3).

### Folds

The opening also sends the row fold $q$ and the values of the committed columns at $r'$. Both are weighted sums of rows,
open to the same solving as the claims, and one column of random values in each batch makes them uniform.

The evaluation argument batches the columns of every table with powers of $\eta$ and opens the batch at $r'$, as
[PIOP Protocol](https://oumuamua.dev/hekate/docs/advanced/piop-protocol#opening-the-commitment) describes. With zero knowledge on, every
table commits uniform random columns in the batch of columns committed whole, and a table with packed
bit columns adds a random column of 128 bits to the packed batch. Their rows add a uniform vector to $q$ and a uniform
term to each value at $r'$, and what the verifier sees is uniform apart from the one claim the opening has to meet.
Random coefficients in every committed row, with a random polynomial batched into the opening, follow Diamond's
zero-knowledge commitment for binary fields (Diamond 2025, Construction 4.1, built on BaseFold).

## One-time pads

Every sumcheck round value, column claim, bus sum and helper value travels as $v' = v + p$, where $p$ is an entry of a
pad used once.

Addition in GF(2^128) is XOR, and subtraction is the same operation: $v = v' + p$. With $p$ uniform and used for one
number only, $v'$ is uniform whatever $v$ is. This is the one-time pad, on field elements.

### Which numbers

Each table pads the first three groups of numbers, and the proof pads the last once:

| Numbers                                                    | Pad entries          |
|:-----------------------------------------------------------|:---------------------|
| ZeroCheck rounds                                           | $n \cdot d$          |
| Column claims at $r_{\text{final}}$, this row and the next | $2\,(V + K + R + B)$ |
| Claimed bus sums and helper values                         | $2B$                 |
| Evaluation sumcheck rounds                                 | $2\nu$               |

The table has $2^n$ rows and sumcheck degree $d$. $V$ counts its columns as the constraints read them, one per packed
bit. $K$ counts the random columns of the previous section, $R = 128$ the bits of the packed random column when the table
packs bits and 0 otherwise, and $B$ the number of bus endpoints on the table; $\nu$ is the larger of the tallest table's $n$
and the log of the grid's width. A round sends $d$ values, since its value at 0 follows from the claim: in characteristic 2,
$g(0)$ is the claim plus $g(1)$. The last claim of a sumcheck takes no entry, because the masked rounds fix it.

The verifier computes the count from the program, the `Config` and the table heights, and hands out one pad index to each masked number it
reads, in the order the prover masked them. The same count sizes the outer statement.

### Committed before the first challenge

The pad comes from the seed you pass to `prove`: entry $i$ is a hash of a seed derived from it and of $i$. The prover
commits to the pad as the first oracle of the outer argument, and the transcript absorbs its root after the trace root
and before the first challenge. A prover that picked its pad after seeing a challenge could fit a masked number to any
value, and the root in the transcript fixes every entry first.

### The replay on masked values

Every check inside a sumcheck is affine, and an affine step run on masked numbers gives the true result plus a
combination of pad entries with coefficients the verifier knows.

In each round the verifier recovers $g(0)$ from the running claim and $g(1)$, and the next claim is $g(r)$: the round's
values combined with Lagrange weights at the challenge $r$. On masked values, the running claim after round $i$ is the
true one plus a mask $m_i$:

$$
m_i = L_0(r_i)\,\big(m_{i-1} + p_{i,1}\big) + \sum_{t=1}^{d} L_t(r_i)\, p_{i,t}
$$

Here $p_{i,t}$ pads the round's value at its point $t$, and $L_t$ is the Lagrange weight of that point. ZeroCheck
starts from the claimed bus sums combined with powers of $\alpha$, and its $m_0$ combines their pad entries the same
way. Every coefficient is public once its challenge is drawn. The verifier replays each sumcheck on the masked rounds,
ends on the masked last claim, and hands the form $m_n$ that separates it from the true one to the outer statement.

Two kinds of check do not survive the replay: the check at $r_{\text{final}}$ multiplies column claims, and the bus
sums have to cancel in plaintext. Those, and every comparison the verifier would make on a value it now sees masked,
become rows of the outer statement.

## The outer statement

The outer statement collects every plaintext check of a proof as rows over two kinds of unknowns: the pad entries and
the wires of each multiplication.

An affine row says that a combination of unknowns with public coefficients equals a public constant. A multiplication
row says $x \cdot y = z$ for three wires. The coefficients and the constants come from the challenges and the masked
numbers, and both sides derive the same statement: the verifier from the proof, the prover from its trace. Padding what a
verifier reads and proving its checks in an outer argument is a known compiler: Frigo and shelat (2024) state it for
Ligero, and the Binius64 specification (sec. 7.1) and VEIL (Dalal et al. 2026) use it.

### From a constraint to rows

A column claim enters the statement as $c' + p$, the published masked claim plus its pad entry, which is affine in the
pad. Sums and scalings of affine forms stay affine. Each multiplication in a constraint gets three wires, left, right
and product: two affine rows pin the left and right wires to the forms of its operands, a multiplication row ties the
three, and the product wire stands for the multiplication from there up.

The checksum of [AIR Constraints](https://oumuamua.dev/hekate/docs/basics/air-constraints) has one constraint, `xor_step`, and one
multiplication:

![The constraint col(active) times the sum of next(acc), col(acc) and col(word), drawn as a tree: a multiplication over the leaf a and a sum of the leaves n, c and w. Each leaf is its published masked claim plus a pad entry, from a′ plus p1 to w′ plus p4. The multiplication gets three wires, L, R and P, and four outer rows follow: L equals a′ plus p1, R equals n′ plus p2 plus c′ plus p3 plus w′ plus p4, L times R equals P, and P joins the table's sum at r_final.](https://oumuamua.dev/assets/hekate/docs/advanced/zero-knowledge/constraint-rows.9bde28573877a18e3a5f4945dfc7156ed860f3d55dfc8213ac4a81dd30a09702.svg "xor_step in the outer statement: one multiplication, three wires, two affine rows.")

### Every row of a proof

Each table contributes the first three, and the proof the last two:

- **The consistency row.** The ZeroCheck's last claim minus its mask equals the table's sum at $r_{\text{final}}$,
  computed from unmasked claims: each constraint through its wires, weighted by
  $\alpha^k \, \mathrm{eq}(\tau, r_{\text{final}})$, the pinned cells, the random column minus its next row, and the
  bus terms.
- **Fixed-column rows.** A fixed column's claim equals the value the verifier computes from the column's shape.
- **Bus rows.** Each bus endpoint adds a multiplication for $h \cdot \mathit{key}$ with two affine rows that pin its
  operands, and a row that ties the helper value in the ZeroCheck to the helper's claim in the evaluation argument.
- **The evaluation row**, once per proof. The evaluation sumcheck's last claim minus its mask equals the value the
  opening certifies at $r'$.
- **The squaring chain**, once per proof with packed bit columns, from the next section.

One more row per bus id, across the tables, says that the unmasked claimed sums add up to zero. The statement has one
multiplication for each multiplication in the constraints, one for each bus endpoint, and 127 when any table has packed
columns.

## Packed bit columns under pads

Ring switching reads the bits of the claims it reduces, and on masked claims that read leaves a correction no affine row
can state. Hekate certifies it with a chain of 127 squarings per proof, whatever the number of packed claims, and
virtual packing works with zero knowledge on.

### The bits of a masked claim

A table can declare columns of bits that the prover commits packed: Keccak's 1600 state bits ride in 25 columns of 64
bits, while its constraints, and its claims, are about single bits. Ring switching, a reduction by Diamond and Posen,
turns the claims about a packed column's bits into one claim about the packed column. The verifier writes the claimed
values in binary, reads that bit matrix along its other axis and folds it with a challenge $r''$, as
[PIOP Protocol](https://oumuamua.dev/hekate/docs/advanced/piop-protocol#many-claims-one-point) shows in the clear.

Under pads the verifier holds $c' = c + p$ for every claim. Reading bits is linear over GF(2): the bits of $c'$ are the
bits of $c$ XOR the bits of $p$, and the fold of the masked matrix is the true fold plus a correction that depends on
the pad alone:

$$
\Delta = \sum_c a_c \, \varphi(p_c),
\qquad
\varphi(x) = \sum_{u=0}^{127} \mathrm{eq}(r'', u) \, \mathrm{bit}_u(x)
$$

where $a_c$ is the public weight of claim $c$ in the evaluation argument. The outer statement needs $\Delta$ to unmask
the evaluation argument's starting claim. Its rows are affine over GF(2^128), and $\varphi$ is linear over GF(2) only:
no row with public coefficients reads the bits of a hidden pad entry. Diamond, who made the large-field commitment
zero-knowledge, left the step open: "We leave for follow-up work the task of making ring-switching zero-knowledge"
(Diamond 2025, sec. 5).

### Committing the bits

The direct route commits the bits: 128 unknowns per hidden claim, a multiplication row $b \cdot b = b$ for each to hold
it to 0 or 1, and $\varphi$ becomes affine in them. The zero-knowledge wrapper of Binius64 takes it for the one
ring-switching reduction in each of its proofs. Hekate makes a claim for every packed bit of every table, on this row
and the next: Keccak's 1600 state bits alone make 3200 claims, and at 128 multiplications each, 409,600 multiplication
rows for one table.

Packed columns appear across the crates Hekate ships: Keccak, SHA-2, AES, RAM, integer and big-number arithmetic, and
the stages of ML-KEM and ML-DSA. The other way out, committing every bit derived from the witness as a column of its
own, gives up packing across that list.

### One linearized polynomial

Every map from GF(2^128) to itself that is linear over GF(2) is a linearized polynomial: a sum of repeated squares of
the argument with fixed coefficients (Lidl and Niederreiter, sec. 3.4). The fold of ring switching is one:

$$
\varphi(x) = \sum_{j=0}^{127} \mu_j \, x^{2^j},
\qquad
\mu_j = \sum_{u=0}^{127} \mathrm{eq}(r'', u) \, \gamma_u^{2^j}
$$

where $\gamma_0, \dots, \gamma_{127}$ is the trace-dual of Hekate's tower basis, which gives
$\mathrm{bit}_u(x) = \mathrm{Tr}(\gamma_u x)$. Both sides compute the $\mu_j$ once $r''$ is drawn.

Squaring is a field automorphism in characteristic 2, $(x + y)^2 = x^2 + y^2$, and it moves from the hidden pad entries
onto the public coefficients. That regroups the correction into 128 affine forms in the pad, one per power, however many
claims the tables have:

$$
T_j = \sum_c \sigma^{-j}(\mu_j a_c) \, p_c,
\qquad
\Delta = \sum_{j=0}^{127} T_j^{\,2^j}
$$

where $\sigma^{-j}$ undoes $j$ squarings. Horner's rule, with squaring in place of multiplication by the variable,
evaluates the sum in 127 squarings:

$$
X_{127} = T_{127},
\qquad
X_j = T_j + X_{j+1}^2,
\qquad
\Delta = X_0.
$$

![Two ways to certify the correction. Committing the bits: 128 bit unknowns, each with b times b equals b, repeated for every packed claim. The Frobenius-Horner chain, once per proof: the pad entries of all packed claims give 128 affine forms T0 to T127; X127 equals T127, each next X squares the one before and adds the next T, through 125 more links to X1, and a last squaring plus T0 gives Delta, which is X0. 127 squarings, one multiplication row each.](https://oumuamua.dev/assets/hekate/docs/advanced/zero-knowledge/horner.2d96343cef4360709f7a05554095c4c1ec24dd153f3177ad51da7f85350a4efd.svg "The correction for every packed claim of a proof, in 127 squarings.")

Each squaring is a multiplication row over two wires pinned equal, and each link adds two affine rows. A proof with
packed columns adds 127 multiplications, 254 affine rows and 381 wires to the statement, whatever the heights and however
many packed claims it has. The verifier still reads the bits of the masked claims as it does in the clear, and the
correction is all that moves into the outer statement. Virtual packing stays as it was: the prover commits bits packed,
and zero knowledge costs a proof with packed tables 127 multiplications.

[Zero-Knowledge Ring-Switching without the Transpose](https://oumuamua.dev/blog/zk-ring-switching) proves that the chain has exactly one
satisfying assignment, that it gives $\Delta$, and that the padded reduction is zero-knowledge whenever the outer
argument is. It also measures the coordinate-matrix gadget of the Binius64 wrapper at the same degree: 16,384
multiplications between hidden wires for one reduction.

## The zk-Ligero argument

Ligero proves that a committed vector satisfies affine rows and multiplication rows, with Reed-Solomon codes, Merkle
trees and no trusted setup. Hekate runs its zero-knowledge form over GF(2^128) on the
outer statement, after the journal version of Ligero by Ames, Hazay, Ishai and Venkitasubramaniam.

### Two committed oracles

The unknowns go into rows of $l$ symbols, in two oracles:

- **The pad oracle** holds the pad entries. It is committed first, and its root enters the transcript before any
  challenge.
- **The auxiliary oracle** holds the wires in three aligned blocks, with the left, right and product wire of a
  multiplication at the same position of the same row of their blocks, and five mask rows after them. Its root comes
  after the tables, since the wire values exist only once the tables' challenges are drawn.

Each row is extended with $t + 1$ random symbols to $k = l + t + 1$ symbols, where $t$ is the number of columns the
verifier opens, and encoded at rate 1/16 into $n = 16k$ symbols. The row's $k$ symbols are its values on one additive
subspace of the field, and the codeword holds its values on a disjoint coset: no opened symbol is a message symbol. Each
oracle is a Merkle tree over the columns of its encoded rows.

![The two oracles of the outer argument, each row l symbols followed by t plus 1 random ones. The pad oracle holds the pad entries, and its root comes first. The auxiliary oracle holds blocks of left, right and product wires and five mask rows, one interleaved, two linear and two quadratic, the first quadratic one with a zero message, and its root comes last. One position is outlined in all three wire blocks: one multiplication, L times R equals P. Each row of k symbols encodes into n equals 16k, and both trees open the same t columns.](https://oumuamua.dev/assets/hekate/docs/advanced/zero-knowledge/ligero.cf9890fc0ef7fd293fe565ab2a70e9a1d51e923a193782d13f8ffe7c655190ea.svg "The pad and auxiliary oracles of the outer argument. Sizes are illustrative.")

### Three tests

After the auxiliary root, the verifier draws challenges, and the prover answers each with one polynomial, absorbed
before the next challenge:

1. **Interleaved.** A random combination of every row plus the interleaved mask, of degree below $k$. A row far from
   every codeword makes the combination far from the code, and the test holds every committed row close to a codeword.
2. **Linear.** The affine rows, combined with weights from a tensor of $\lceil \log_2 M \rceil$ challenges for $M$
   rows. The weights that fall on each oracle row become a polynomial, and the prover sends the sum of weight
   polynomials times row polynomials, plus the linear mask, of degree below $2k$. Its values on the subspace must add
   up to the combined constant: every affine row holds.
3. **Quadratic.** Left times right minus product for each aligned triple of rows, combined with one challenge per
   triple, plus the quadratic mask, of degree below $2k$. It must vanish at every message position: every
   multiplication holds.

The verifier then draws $t$ column positions, and the prover opens both oracles there with Merkle multiproofs. At every
opened position, each response must equal the same combination of the opened symbols, and a response that does not come
from the committed rows disagrees with them at a large share of positions.

### What the outer proof hides

The responses are combinations of hidden rows, and each one carries a mask that is uniform over the whole space its test
allows:

| Test        | Mask                                                                          | Rows |
|:------------|:------------------------------------------------------------------------------|-----:|
| Interleaved | A random codeword                                                             |    1 |
| Linear      | A random polynomial of degree below $2k$ whose values on the subspace sum to zero |    2 |
| Quadratic   | A random polynomial of degree below $2k$ that vanishes on the message positions |    2 |

A polynomial of degree below $2k$ takes two rows to cover: a low row, and a high row multiplied by $Z_k$, the polynomial
that vanishes on the subspace. A low row alone reaches fewer than half the dimensions of either space, and the response
would then carry information about the rows.

Row privacy covers the openings. With $t + 1$ random symbols in every row, any $t$ opened symbols of a row are uniform
and independent of its message: Ligero's Lemma 4.15, which asks for $k > l + t$. The masks and the random symbols
together make the outer argument perfect honest-verifier zero-knowledge. A simulator draws the opened symbols and the
responses uniformly from their allowed spaces and solves for the mask rows' symbols at the opened positions.

### Soundness and size

A cheating prover passes one opened column with probability at most $(n + 2k)/(2n)$: Lemmas 4.6 and 4.10 of the Ligero
journal version, balanced at distance $e = (n - 2k)/2$. At rate 1/16 that is 9/16, about 0.83 bits per column.
`outer_queries` in the `Config` sets $t$, and `Config::prod()` takes the fewest columns that clear its 100-bit floor.
Two terms do not shrink with $t$: Lemma 4.6 adds $n / 2^{128}$, which the cap $n \le 2^{24}$ keeps at 104 bits or more,
and the tensor of the linear test adds $\lceil \log_2 M \rceil / 2^{128}$.

Both sides derive $l$, $k$, $n$ and $t$ from the number of multiplications and the `Config`; the pad entries set only
the number of pad rows. The proof carries none of them: a geometry the prover could choose would be a free parameter, and the
query bound would no longer hold. A statement too large for the domain cap is rejected.

## Why the proof is zero-knowledge

A simulator that holds the statement and the verifier's challenges, and no witness, produces a view distributed like a
real proof's, up to breaking the seed's generator or the hash.

1. It commits an all-zero trace with real support and real random columns. The roots, the opened columns, the folds and
   the values at $r'$ are distributed as in a real proof.
2. It picks plaintext numbers that pass every check for its challenges: column claims of its choice, the ZeroCheck's
   last value from the constraints, each sumcheck round with its free coefficient, bus sums that cancel bus by bus, and
   each evaluation argument ending on the zero trace's value. Every step is a linear solve with a free coordinate.
3. It draws the published numbers uniformly and sets the pad to their difference from the plaintexts.
4. It builds the outer argument from the published numbers, the challenges and the public constants alone, as the
   previous section describes.

In a real proof the published numbers are uniform because the pad is, the outer openings by row privacy, and the
responses by their masks. A Hekate proof is non-interactive, and its challenges are hashes of the transcript: the
simulator programs the hash as a random oracle, fixing the published values first and answering each transcript prefix
with the challenge it chose. The distinguishing advantage is bounded by four terms: the generator of the pad, the
generator of the support and the random columns, the hash, and a statistical term of about the number of bus rows
divided by $2^{128}$, the chance that a LogUp denominator $\gamma + \mathit{key}$ is zero on some row.

The outer part runs as a test. [`simulated_view_verifies`][sim] builds both roots, both openings and the three responses
from the challenges and the public constants, and passes every check of the outer verifier at fixed challenges; no
simulator passes the shipped verifier, whose challenges are hashes. The hiding of the commitment follows two published
constructions, the randomizable support of the Binius64 specification and Diamond's, which Hekate cites and does not
re-prove.

## What stays with you

Zero knowledge covers the bytes of the proof. Three things stay with the code that calls the prover.

- **The seed.** Draw the 32 bytes you pass to `prove` fresh from a cryptographic random generator for every proof. Every
  pad entry and random symbol comes from it, and one seed used for two different witnesses repeats the support and the
  random columns: opened symbols at a shared position then differ by the encoded difference of the two traces.
- **The prover variant.** The `public` prover runs variable-time arithmetic, and its timing depends on the values it
  proves. Prove private witnesses with `ct`: zero knowledge hides the trace from the proof, and a timing channel on the
  proving device is a separate leak.
- **The shape.** The program, the public inputs and every table height are public. Choose heights from public data: a
  height that follows a secret, such as the length of a hashed message, discloses which power of two it fits in.

`Config { zero_knowledge: false, ..Config::default() }` proves in the clear, faster and with a smaller proof, and it
reveals what [the example above](https://oumuamua.dev/hekate/docs/advanced/zero-knowledge#what-a-proof-in-the-clear-gives-away) solves for: keep it for data that is public
anyway. `Config::dev()` turns zero knowledge off as well. [Benchmarks](https://oumuamua.dev/hekate/benchmarks) lists both modes for each
workload.

## Next steps

- [PIOP Protocol](https://oumuamua.dev/hekate/docs/advanced/piop-protocol): the claims the pads hide, from sumcheck to the opening.
- [Prover Engine](https://oumuamua.dev/hekate/docs/basics/prover-engine): the four steps of the prover, and the seed it takes.
- [LogUp Buses](https://oumuamua.dev/hekate/docs/basics/logup-buses): the helper columns and bus sums the outer statement settles.
- [Zero-Knowledge Ring-Switching without the Transpose](https://oumuamua.dev/blog/zk-ring-switching): the squaring chain, with its proofs.

[census]: https://github.com/oumuamua-labs/hekate/blob/main/hekate/tests/wire_census.rs
[sim]: https://github.com/oumuamua-labs/hekate/blob/main/hekate-program/tests/outer_hiding.rs