zkWHIR 3.0 : Code Switch Protocol (part 1)#249
Conversation
Merging this PR will not alter performance
Comparing Footnotes
|
recmo
left a comment
There was a problem hiding this comment.
Most import is we need need a proptest based on arbitrary config testing all invariants.
WizardOfMenlo
left a comment
There was a problem hiding this comment.
Approved, left some comments. The most annoying footgun is that we should check what is the right zero-evader to use.
Here is what AI had to say about it:
[P2] src/protocols/code_switch.rs:207 — target_interleaving_depth > 1 makes proving panic
Config::new allows any positive target_interleaving_depth and sets the target IRSvector_size to source_message_length * target_interleaving_depth, but prove still passes a vector whose length is only source_message_length into
self.target.commit. For any caller using the advertised target_interleaving_depth > 1, IrsConfig::commit rejects the input length and panics. Either restrict this
config to depth 1 or pass a target vector with the length/layout expected by the target IRS config.
### [P2] src/protocols/code_switch.rs:118 — ZK mask lengths can create invalid
IRS configs
In ZK mode, target_config.mask_length and mask_config.mask_length are assigned
after IrsConfig::new has already chosen codeword_length from the unmasked message length. If budget.target or budget.mask exceeds the spare capacity implied by the
original rate, masked_message_length() > codeword_length, so later commit/open calls panic in the NTT evaluation path; even before that, reparameterise_security
computes rates above 1. This arises for valid-looking inputs such as rate 1/2 with a mask budget larger than the message length. The constructor should either
size codeword_length for message_length + mask_length or reject budgets that do not fit.[P2] src/protocols/code_switch.rs:267 — ZK covector update omits the
mask-oracle coefficients
In ZK mode, the verifier updates sum using OOD answers and source openings for
the combined polynomial f(x) + x^n s(x), but the prover-side covector update only
mutates the caller-provided single covector and does not require or return the
mask portion corresponding to s. With the f-length covector used by the current
test and implied by message.len(), honest proofs with nonzero source masks will
produce a verifier sum that includes mask terms while the downstream target
relation only has weights for f. Require/update a covector of length
message.len() + mask_message_len and expose the split for target/mask, or keep
the verifier update f-only.Overall Verdict
needs attention
| let matrix_witness = self.matrix_commit.commit(prover_state, &matrix); | ||
|
|
||
| // Handle out-of-domain points and values | ||
| // TODO : Remove this logic after main whir protocol is updated |
There was a problem hiding this comment.
Not sure we can actually remove this TODO unfortunately
There was a problem hiding this comment.
Why ? we can remove this right we dont need ood points in commitment after we update the protocol as this should be handled by code switch. Here it is just revealing more info about the vector.
There was a problem hiding this comment.
related slack message link : https://atheonlabs.slack.com/archives/C083C5JM8QH/p1775500051740209
|
|
||
| /// Recompute `johnson_slack`, `in_domain_samples`, and `out_domain_samples` | ||
| /// for the current rate (which accounts for `mask_length`). | ||
| pub fn reparameterise_security(&mut self, security_target: f64, unique_decoding: bool) { |
There was a problem hiding this comment.
Can we rename this to something more informative? This basically computes the parameters for this step right?
There was a problem hiding this comment.
how about : recompute_security_parameters ?
| source_config.rate(), | ||
| ); | ||
| mask_config.mask_length = budget.mask; | ||
| mask_config.reparameterise_security(security_target, unique_decoding); |
There was a problem hiding this comment.
Can we make reparametrise security be done in the new of mask_config? This makes it harder to misuse
There was a problem hiding this comment.
Yeah so the thing is this work will be cleaned up in parameter selection PR. here I just added for the correctness of the protocol. The configs are very messed up in each of the sub protocols for eg :
in IRS commit the way mask length is being handled is bad design. Will fix this I have noted these things on my end.
|
|
||
| // Step 1b: s := Enc_{C_zk}((r || padding), r'') — Construction 9.7 Step 1, p.55 | ||
| #[allow(clippy::option_if_let_else)] | ||
| let mask = if let Some(mask_config) = &self.mask_commit { |
There was a problem hiding this comment.
Can we make this a map instead? (Instead of if else)
There was a problem hiding this comment.
It is a design choice, initially it was map and remco pointed out to use the if let pattern. Ig this looks clean even when we need to suppress clippy suggestion of map. with map it looked like :
let (mask_message, mask_witness) =
self.mask_commit
.as_ref()
.map_or((None, None), |mask_config| {
let mask_msg_len = mask_config.message_length();
let r_embedded = lift(self.source.embedding(), source_randomness);
let embedded_randomness_len = r_embedded.len();
let mut mask_msg = Vec::with_capacity(mask_msg_len);
mask_msg.extend_from_slice(&r_embedded);
let random_padding: Vec<M::Target> =
random_vector(prover_state.rng(), mask_msg_len - embedded_randomness_len);
mask_msg.extend_from_slice(&random_padding);
let witness = mask_config.commit(prover_state, &[&mask_msg]);
(Some(mask_msg), Some(witness))
});| None | ||
| }; | ||
|
|
||
| // Step 2-3: OOD challenge + answers — Construction 9.7 Steps 2-3, p.55 |
There was a problem hiding this comment.
We should double check that the current univariate_eval satisfies the private zero-evader conditions that we have in the paper, I am not sure if they do
There was a problem hiding this comment.
Let's leave a comment about it
| // Step 4: in-domain queries — Construction 9.7 Step 4, p.55 | ||
| let source_evaluations = self.source.open(prover_state, &[witness]); | ||
|
|
||
| // Step 5: batching — Construction 9.7 Step 5, p.55 |
There was a problem hiding this comment.
This is still step 4 right?
There was a problem hiding this comment.
yup according to the paper yes. I just divided into different step for batching part.
|
Agreed on the deterministic evader — paper has a private ze_ood(ρ) but the geometric progression (1, α, α², …) works fine: applied to [f; (r,s)] it just evaluates the concatenated polynomial at α. ZK still holds because y = f(α) + α^ℓ · (r,s)(α) is uniform whenever (r,s) has fresh randomness and α ≠ 0. The α = 0 event is 1/|F| and folds into the soundness budget. So as long as ℓ_zk - r ≥ t_ood we get perfect privacy ζ_ze = 0. Soundness parameter derivation pending. |
Add the `code_switch` module with: - `RoundZkConfig`: per-round ZK parameters (query budgets, mask dimensions, OOD sample count) that extend `RoundConfig` without touching the non-ZK path. - `ZkMaskClaim`: coefficient carrier for mask/target batching weights, preventing the covector mismatch bug from worldfnd/whir#249. - `WhirRoundZkProof`: optional per-round ZK proof data (mask commitment + private OOD answers). - Construction 9.7 `mu'` identity unit tests over BabyBear, covering both n=0 and n=2 auxiliary masks, plus OOD and batching helpers. All 187 existing WHIR tests pass unchanged. The implementation itself is blocked on Plonky3#1584 (ZK encoding), Plonky3#1585 (zero-evader helpers), and Plonky3#1586 (HVZK sumcheck).
Add the `code_switch` module with: - `RoundZkConfig`: per-round ZK parameters (query budgets, mask dimensions, OOD sample count) that extend `RoundConfig` without touching the non-ZK path. - `ZkMaskClaim`: coefficient carrier for mask/target batching weights, preventing the covector mismatch bug from worldfnd/whir#249. - `WhirRoundZkProof`: optional per-round ZK proof data (mask commitment + private OOD answers). - Construction 9.7 `mu'` identity unit tests over BabyBear, covering both n=0 and n=2 auxiliary masks, plus OOD and batching helpers. All 187 existing WHIR tests pass unchanged. The implementation itself is blocked on Plonky3#1584 (ZK encoding), Plonky3#1585 (zero-evader helpers), and Plonky3#1586 (HVZK sumcheck).
Add the `code_switch` module with: - `RoundZkConfig`: per-round ZK parameters (query budgets, mask dimensions, OOD sample count) that extend `RoundConfig` without touching the non-ZK path. - `ZkMaskClaim`: coefficient carrier for mask/target batching weights, preventing the covector mismatch bug from worldfnd/whir#249. - `WhirRoundZkProof`: optional per-round ZK proof data (mask commitment + private OOD answers). - Construction 9.7 `mu'` identity unit tests over BabyBear, covering both n=0 and n=2 auxiliary masks, plus OOD and batching helpers. All 187 existing WHIR tests pass unchanged. The implementation itself is blocked on Plonky3#1584 (ZK encoding), Plonky3#1585 (zero-evader helpers), and Plonky3#1586 (HVZK sumcheck).
Summary
Implements the code-switching IOR from Section 9.4 as a standalone sub-protocol in src/protocols/code_switch.rs.
Code switching reduces a proximity claim about oracle f w.r.t. source code C to a proximity claim about oracle g w.r.t. target code C', enabling the polylogarithmic-verifier protocol (Theorem 4) where the tested code shrinks each round.
What's implemented
What's NOT in this PR (next PRs)