zkWHIR 3.0 : Updated protocols for whir round integration and added mask proximity protocol (part 2)#251
Conversation
# Conflicts: # src/protocols/code_switch.rs
Merging this PR will not alter performance
Comparing Footnotes
|
| target_config.interleaving_depth.is_power_of_two(), | ||
| "target.interleaving_depth must be a power of 2" | ||
| ); | ||
| // Theorem 9.6: ℓ_zk ≥ r (mask oracle must cover source randomness). |
There was a problem hiding this comment.
This validates only ℓ_zk ≥ r. For the current OOD map f(α) + α^ℓ·mask(α), Lemma 9.8 also needs a checked privacy/rank condition for ze_ood; otherwise configs with too little fresh s padding can be accepted. Please reject under-padded ZK configs here, or make this constructor explicitly unchecked until parameter selection enforces the condition.
There was a problem hiding this comment.
Added the assert here but would remove it in the parameter selection PR
| /// It must match what the prover received from the same transcript — | ||
| /// not caller-supplied randomness. See `prove` doc for details. | ||
| /// | ||
| /// Returns the target commitment. In ZK mode, the caller **must** |
There was a problem hiding this comment.
This is soundness-critical and should not be a doc-only caller obligation. Construction 9.7’s next relation includes the mask oracle s; code_switch::verify currently returns only g. Please make ZK code-switch return/accept a typed next-claim including the mask commitment, or provide a composed verifier that runs code-switch + mask-proximity in a fixed transcript order.
There was a problem hiding this comment.
The idea is to have orchestrator own the masks and commit them. pass the refs in sub protocols and if there is tampering the mask proximity check should fail.
| // Step 2: compute and send combined polynomials + IRS randomness | ||
| let irs_masks_per_vector = | ||
| self.c_zk_commit.mask_length * self.c_zk_commit.interleaving_depth; | ||
| debug_assert_eq!( |
| @@ -309,65 +292,60 @@ impl<M: Embedding> Config<M> { | |||
| Hash: ProverMessage<[H::U]>, | |||
| { | |||
| assert_eq!( | |||
There was a problem hiding this comment.
verify uses assert_eq! for folding_randomness shape. Library verifiers should return VerificationError, not panic. Convert to verify! or structured verifier error.
| }; | ||
| } | ||
|
|
||
| // Even more trivial non-zk protocol: send f an r directly. |
There was a problem hiding this comment.
Nit: “send f an r directly” → “send f and r directly”
|
|
||
| /// Prover output from the commit phase. | ||
| #[must_use] | ||
| pub struct Witness<F: Field> { |
There was a problem hiding this comment.
Witness::mask_witness and Witness::fresh_msgs are public. External mutation between commit and prove can cause invalid proofs. Not a soundness leak, but avoidable API footgun. Make fields private or pub(crate).
| .prove(prover_state, &mut vector, &mut covector, &mut sum, &[]) | ||
| .0; | ||
| .round_challenges; | ||
| assert!(!vector[0].is_zero(), "Proof failed"); |
There was a problem hiding this comment.
Here and at :107, :135. These three assert!(_, "Proof failed") panic the prover on (a) all-zero input vector, (b) mask_rlc == 0 (1-in-2^64), (c) zero post-fold masked vector. An honest prover hitting any of these in deployment should Err and let upstream re-randomize, not crash. Consider a pub enum ProverError { Degenerate, ChallengeIsZero } and return Result<Opening<F>, ProverError>.
There was a problem hiding this comment.
Wait why? The all-zero input vector is a valid input vector.
| // Send mask sum and get combination randomness. | ||
| let mut mask_sum = F::ZERO; | ||
| let mut mask_rlc = F::ONE; | ||
| if !masks.is_empty() { |
There was a problem hiding this comment.
Here and at :183. Prover gates ZK path on !masks.is_empty() here; verifier gates on mask_length > 0 && num_rounds > 0 at line 183. Equivalent only because of the assert_eq!(masks.len(), num_rounds * mask_length) at line 92. Can we pick one expression and use it on both sides, preferably mask_length > 0 && num_rounds > 0 since the verifier doesn't have masks in scope. Reduces the chance of a future refactor desynchronizing them silently.
| } | ||
|
|
||
| /// Length of the covector for this code-switch. | ||
| pub fn covector_length(&self) -> usize { |
There was a problem hiding this comment.
nit: the .max() is dead code given the asserts at lines 95 (message_mask_length >= source.mask_length when ZK) and 110 (source.mask_length == 0 when not ZK). Either of these forces message_mask_length >= source.mask_length always, so this can be + self.message_mask_length.
| .prove(prover_state, &mut vector, &mut covector, &mut sum, &[]) | ||
| .0; | ||
| .round_challenges; | ||
| assert!(!vector[0].is_zero(), "Proof failed"); |
There was a problem hiding this comment.
Wait why? The all-zero input vector is a valid input vector.
Summary
Mask Proximity Protocol
Implements Construction 7.2 from the paper (§7, p.43-48), specialized for zero-constraint masks (μ_i = 0, sl_{o,i} = 0).
Protocol flow:
Important
Soundness (Lemma 7.4, p.45): If ξ_i is δ-far from every C_zk codeword, then ξ*_i = s_i + γ·ξ_i is also far from s_i + γ·c for every codeword c, with high probability over γ. The spot-check catches disagreement at random positions with probability ≥ 1 − (1−δ)^{t_zk}.
Important
ZK safety (Lemma 7.3, p.44): Only ξ*_i = s_i + γ·ξ_i is revealed in full. Since s_i is uniformly random, ξ*_i is uniform regardless of ξ_i — the original mask is never exposed. The tree is opened at ≤ t_zk positions, within the ZK query budget of C_zk's encoding.
Next Steps