Skip to main content

proof_cat/
prove.rs

1//! End-to-end proof generation and verification.
2//!
3//! Bridges plonkish-cat's [`ConstraintSet`] with the sumcheck
4//! protocol: given a constraint set and a satisfying witness,
5//! [`prove`] produces a [`Proof`]; [`verify`] checks it.
6//!
7//! The current protocol is binding but **not** zero-knowledge:
8//! all wire values are opened via Merkle proofs.  A future
9//! version will replace Merkle openings with polynomial
10//! commitment evaluations for full zero-knowledge.
11
12use field_cat::{Field, FieldBytes};
13use plonkish_cat::{Constraint, ConstraintSet, Expression, Wire};
14
15use crate::commit::merkle::{MerkleProof, MerkleRoot, MerkleTree};
16use crate::error::Error;
17use crate::poly::MultilinearPoly;
18use crate::sumcheck::{SumcheckClaim, SumcheckProof, sumcheck_prove, sumcheck_verify};
19use crate::transcript::Transcript;
20
21/// Domain separation label for the proof transcript.
22const TRANSCRIPT_LABEL: &[u8] = b"proof-cat-v0.1";
23
24// ── Types ────────────────────────────────────────────────────
25
26/// A witness: wire values satisfying the constraint set.
27///
28/// Entry `i` is the value of [`Wire::new(i)`](plonkish_cat::Wire).
29/// The vector must be long enough to cover every wire index
30/// referenced by the constraint set.
31///
32/// # Examples
33///
34/// ```
35/// use field_cat::F101;
36/// use proof_cat::Witness;
37///
38/// // Three wires: w0=3, w1=4, w2=7.
39/// let w = Witness::new(vec![F101::new(3), F101::new(4), F101::new(7)]);
40/// assert_eq!(w.values().len(), 3);
41/// ```
42#[derive(Debug, Clone)]
43pub struct Witness<F: Field> {
44    values: Vec<F>,
45}
46
47impl<F: Field> Witness<F> {
48    /// Create a witness from a vector of wire values.
49    #[must_use]
50    pub fn new(values: Vec<F>) -> Self {
51        Self { values }
52    }
53
54    /// The wire values.
55    #[must_use]
56    pub fn values(&self) -> &[F] {
57        &self.values
58    }
59
60    /// Build an assignment closure for constraint evaluation.
61    fn assignment(&self) -> impl Fn(Wire) -> Result<F, plonkish_cat::Error> + '_ {
62        |wire| {
63            self.values
64                .get(wire.index())
65                .cloned()
66                .ok_or(plonkish_cat::Error::WireOutOfBounds {
67                    wire_index: wire.index(),
68                    allocated: self.values.len(),
69                })
70        }
71    }
72}
73
74/// An opened wire value with its Merkle proof.
75#[derive(Debug, Clone)]
76pub struct WireOpening<F: Field> {
77    wire_index: usize,
78    value: F,
79    merkle_proof: MerkleProof,
80}
81
82impl<F: Field> WireOpening<F> {
83    /// The wire index.
84    #[must_use]
85    pub fn wire_index(&self) -> usize {
86        self.wire_index
87    }
88
89    /// The opened value.
90    #[must_use]
91    pub fn value(&self) -> &F {
92        &self.value
93    }
94
95    /// The Merkle proof.
96    #[must_use]
97    pub fn merkle_proof(&self) -> &MerkleProof {
98        &self.merkle_proof
99    }
100}
101
102/// A complete proof of constraint satisfaction.
103#[derive(Debug, Clone)]
104pub struct Proof<F: Field> {
105    witness_commitment: MerkleRoot,
106    sumcheck: SumcheckProof<F>,
107    wire_openings: Vec<WireOpening<F>>,
108}
109
110impl<F: Field> Proof<F> {
111    /// The Merkle root committing to the witness.
112    #[must_use]
113    pub fn witness_commitment(&self) -> &MerkleRoot {
114        &self.witness_commitment
115    }
116
117    /// The sumcheck proof.
118    #[must_use]
119    pub fn sumcheck_proof(&self) -> &SumcheckProof<F> {
120        &self.sumcheck
121    }
122
123    /// The opened wire values with Merkle proofs.
124    #[must_use]
125    pub fn wire_openings(&self) -> &[WireOpening<F>] {
126        &self.wire_openings
127    }
128}
129
130// ── Proving ──────────────────────────────────────────────────
131
132/// Produce a proof that a witness satisfies a constraint set.
133///
134/// # Protocol
135///
136/// 1. Convert copy constraints to polynomial constraints.
137/// 2. Validate the witness satisfies all constraints.
138/// 3. Commit to the witness via a Merkle tree.
139/// 4. Evaluate each constraint (all zero for valid witness).
140/// 5. Build a multilinear polynomial from the evaluations.
141/// 6. Run sumcheck to prove the evaluations sum to zero.
142/// 7. Open all wire values with Merkle proofs.
143///
144/// # Errors
145///
146/// Returns an error if the witness does not satisfy the
147/// constraints, or if any internal step fails.
148///
149/// # Examples
150///
151/// ```
152/// use field_cat::F101;
153/// use plonkish_cat::{Constraint, ConstraintSet, Expression, Wire};
154/// use proof_cat::{Witness, prove, verify};
155///
156/// // Constraint: w2 - w0 - w1 = 0  (addition gate).
157/// let expr = Expression::Wire(Wire::new(2))
158///     - Expression::Wire(Wire::new(0))
159///     - Expression::Wire(Wire::new(1));
160/// let cs = ConstraintSet::empty()
161///     .with_constraint(Constraint::new(expr));
162///
163/// // Valid witness: 3 + 4 = 7.
164/// let witness = Witness::new(vec![
165///     F101::new(3), F101::new(4), F101::new(7),
166/// ]);
167///
168/// let proof = prove(&cs, &witness)?;
169/// assert!(verify(&cs, &proof)?);
170/// # Ok::<(), proof_cat::Error>(())
171/// ```
172pub fn prove<F: FieldBytes>(
173    constraints: &ConstraintSet<F>,
174    witness: &Witness<F>,
175) -> Result<Proof<F>, Error> {
176    // 1. Flatten: convert copy constraints to polynomial form.
177    let all_constraints = flatten_constraints(constraints);
178
179    if all_constraints.is_empty() {
180        Err(Error::EmptyConstraintSet)
181    } else {
182        // 2. Validate witness satisfies all constraints.
183        validate_witness(&all_constraints, witness)?;
184
185        // 3. Commit witness to Merkle tree.
186        let tree = MerkleTree::from_field_values(witness.values());
187
188        // 4. Evaluate each constraint (should all be zero).
189        let evals = evaluate_constraints(&all_constraints, witness)?;
190
191        // 5. Pad to power-of-two length and build MLE.
192        let padded = pad_to_power_of_two(evals);
193        let poly = MultilinearPoly::from_evals(padded)?;
194
195        // 6. Initialize transcript and run sumcheck.
196        let transcript = Transcript::new(TRANSCRIPT_LABEL)
197            .absorb_bytes(tree.root().as_bytes())
198            .absorb_bytes(&all_constraints.len().to_le_bytes());
199
200        let (sumcheck, _, _) = sumcheck_prove(&SumcheckClaim::new(poly, F::zero()), transcript)?;
201
202        // 7. Open all wire values.
203        let wire_openings: Result<Vec<WireOpening<F>>, Error> = (0..witness.values().len())
204            .map(|i| {
205                let merkle_proof = tree.open(i)?;
206                Ok(WireOpening {
207                    wire_index: i,
208                    value: witness.values()[i].clone(),
209                    merkle_proof,
210                })
211            })
212            .collect();
213
214        Ok(Proof {
215            witness_commitment: tree.root(),
216            sumcheck,
217            wire_openings: wire_openings?,
218        })
219    }
220}
221
222// ── Verification ─────────────────────────────────────────────
223
224/// Verify a proof of constraint satisfaction.
225///
226/// The verifier does not need the witness; it works entirely
227/// from the [`Proof`] (which contains Merkle-opened wire values)
228/// and the public [`ConstraintSet`].
229///
230/// # Protocol
231///
232/// 1. Flatten copy constraints to polynomial form.
233/// 2. Replay the transcript and run the sumcheck verifier.
234/// 3. Verify all Merkle openings against the committed root.
235/// 4. Re-evaluate constraints from the opened wire values.
236/// 5. Check the sumcheck final evaluation matches.
237///
238/// Returns `true` if the proof is valid.
239///
240/// # Errors
241///
242/// Returns an error if any verification step encounters
243/// a structural problem (wrong round count, etc.).
244///
245/// # Examples
246///
247/// ```
248/// use field_cat::F101;
249/// use plonkish_cat::{Constraint, ConstraintSet, Expression, Wire};
250/// use proof_cat::{Witness, prove, verify};
251///
252/// // Constraint: w1 - w0 * w0 = 0  (squaring).
253/// let expr = Expression::Wire(Wire::new(1))
254///     - Expression::Wire(Wire::new(0)) * Expression::Wire(Wire::new(0));
255/// let cs = ConstraintSet::empty()
256///     .with_constraint(Constraint::new(expr));
257///
258/// // Witness: 7^2 = 49.
259/// let proof = prove(
260///     &cs,
261///     &Witness::new(vec![F101::new(7), F101::new(49)]),
262/// )?;
263///
264/// // Verification succeeds.
265/// assert!(verify(&cs, &proof)?);
266/// # Ok::<(), proof_cat::Error>(())
267/// ```
268pub fn verify<F: FieldBytes>(
269    constraints: &ConstraintSet<F>,
270    proof: &Proof<F>,
271) -> Result<bool, Error> {
272    // 1. Flatten constraints.
273    let all_constraints = flatten_constraints(constraints);
274
275    if all_constraints.is_empty() {
276        Err(Error::EmptyConstraintSet)
277    } else {
278        // 2. Replay transcript and run sumcheck verifier.
279        let padded_len = pad_to_power_of_two_len(all_constraints.len());
280        let num_vars = usize::try_from(padded_len.trailing_zeros())
281            .map_err(|_| Error::NotPowerOfTwo { value: padded_len })?;
282
283        let transcript = Transcript::new(TRANSCRIPT_LABEL)
284            .absorb_bytes(proof.witness_commitment.as_bytes())
285            .absorb_bytes(&all_constraints.len().to_le_bytes());
286
287        let (final_eval, challenges, _) = sumcheck_verify(
288            &proof.sumcheck,
289            &F::zero(),
290            crate::poly::NumVars::new(num_vars),
291            transcript,
292        )?;
293
294        // 3. Verify all Merkle openings.
295        let all_openings_valid = proof.wire_openings.iter().all(|opening| {
296            MerkleTree::verify_opening(
297                &proof.witness_commitment,
298                opening.wire_index,
299                &opening.value,
300                &opening.merkle_proof,
301            )
302        });
303
304        if all_openings_valid {
305            // 4. Build assignment from opened wire values.
306            let assignment = build_assignment_from_openings(&proof.wire_openings);
307
308            // 5. Re-evaluate constraints.
309            let evals = evaluate_constraints_with(&all_constraints, &assignment)?;
310            let padded = pad_to_power_of_two(evals);
311            let poly = MultilinearPoly::from_evals(padded)?;
312
313            // 6. Check MLE evaluation at challenges matches sumcheck claim.
314            let expected_eval = poly.evaluate(&challenges)?;
315            Ok(expected_eval == final_eval)
316        } else {
317            Err(Error::MerkleVerificationFailed)
318        }
319    }
320}
321
322// ── Helpers ──────────────────────────────────────────────────
323
324/// Convert copy constraints to polynomial constraints and merge
325/// with the existing polynomial constraints.
326fn flatten_constraints<F: Field>(cs: &ConstraintSet<F>) -> Vec<Constraint<F>> {
327    let polynomial: Vec<Constraint<F>> = cs.constraints().to_vec();
328    let from_copies: Vec<Constraint<F>> = cs
329        .copy_constraints()
330        .iter()
331        .map(|cc| {
332            let expr = Expression::Wire(cc.left()) - Expression::Wire(cc.right());
333            Constraint::new(expr)
334        })
335        .collect();
336    polynomial.into_iter().chain(from_copies).collect()
337}
338
339/// Validate that the witness satisfies all constraints.
340fn validate_witness<F: Field>(
341    constraints: &[Constraint<F>],
342    witness: &Witness<F>,
343) -> Result<(), Error> {
344    let assign = witness.assignment();
345    constraints.iter().enumerate().try_for_each(|(i, c)| {
346        c.is_satisfied(&assign).map_err(Error::from).and_then(|ok| {
347            if ok {
348                Ok(())
349            } else {
350                Err(Error::UnsatisfiedConstraint { index: i })
351            }
352        })
353    })
354}
355
356/// Evaluate all constraints against the witness.
357fn evaluate_constraints<F: Field>(
358    constraints: &[Constraint<F>],
359    witness: &Witness<F>,
360) -> Result<Vec<F>, Error> {
361    let assign = witness.assignment();
362    constraints
363        .iter()
364        .map(|c| c.expression().evaluate(&assign).map_err(Error::from))
365        .collect()
366}
367
368/// Evaluate constraints against an opened-value assignment.
369fn evaluate_constraints_with<F: Field>(
370    constraints: &[Constraint<F>],
371    assignment: &impl Fn(Wire) -> Result<F, plonkish_cat::Error>,
372) -> Result<Vec<F>, Error> {
373    constraints
374        .iter()
375        .map(|c| c.expression().evaluate(assignment).map_err(Error::from))
376        .collect()
377}
378
379/// Build an assignment function from wire openings.
380fn build_assignment_from_openings<F: Field>(
381    openings: &[WireOpening<F>],
382) -> impl Fn(Wire) -> Result<F, plonkish_cat::Error> + '_ {
383    move |wire| {
384        openings
385            .iter()
386            .find(|o| o.wire_index == wire.index())
387            .map(|o| o.value.clone())
388            .ok_or(plonkish_cat::Error::WireOutOfBounds {
389                wire_index: wire.index(),
390                allocated: openings.len(),
391            })
392    }
393}
394
395/// Pad a vector to the next power-of-two length with `F::zero()`.
396fn pad_to_power_of_two<F: Field>(v: Vec<F>) -> Vec<F> {
397    let target = pad_to_power_of_two_len(v.len());
398    let padding_count = target - v.len();
399    v.into_iter()
400        .chain((0..padding_count).map(|_| F::zero()))
401        .collect()
402}
403
404/// The next power of two >= n (minimum 1).
405fn pad_to_power_of_two_len(n: usize) -> usize {
406    if n <= 1 { 1 } else { n.next_power_of_two() }
407}
408
409#[cfg(test)]
410mod tests {
411    use super::*;
412    use field_cat::F101;
413    use plonkish_cat::{CopyConstraint, Expression, Wire};
414
415    #[test]
416    fn add_gate_prove_verify() -> Result<(), Error> {
417        // Circuit: add gate.  Wires: in0=w0, in1=w1, out=w2.
418        // Constraint: w2 - w0 - w1 = 0.
419        let expr = Expression::Wire(Wire::new(2))
420            - Expression::Wire(Wire::new(0))
421            - Expression::Wire(Wire::new(1));
422        let cs = ConstraintSet::empty().with_constraint(Constraint::new(expr));
423
424        // Valid witness: 3 + 4 = 7.
425        let witness = Witness::new(vec![F101::new(3), F101::new(4), F101::new(7)]);
426        let proof = prove(&cs, &witness)?;
427        let valid = verify(&cs, &proof)?;
428        assert!(valid);
429        Ok(())
430    }
431
432    #[test]
433    fn mul_gate_prove_verify() -> Result<(), Error> {
434        // Constraint: w2 - w0 * w1 = 0.
435        let expr = Expression::Wire(Wire::new(2))
436            - Expression::Wire(Wire::new(0)) * Expression::Wire(Wire::new(1));
437        let cs = ConstraintSet::empty().with_constraint(Constraint::new(expr));
438
439        // Valid witness: 5 * 6 = 30.
440        let witness = Witness::new(vec![F101::new(5), F101::new(6), F101::new(30)]);
441        let proof = prove(&cs, &witness)?;
442        assert!(verify(&cs, &proof)?);
443        Ok(())
444    }
445
446    #[test]
447    fn invalid_witness_rejected() {
448        let expr = Expression::Wire(Wire::new(2))
449            - Expression::Wire(Wire::new(0))
450            - Expression::Wire(Wire::new(1));
451        let cs = ConstraintSet::empty().with_constraint(Constraint::new(expr));
452
453        // Invalid: 3 + 4 != 8.
454        let witness = Witness::new(vec![F101::new(3), F101::new(4), F101::new(8)]);
455        let result = prove(&cs, &witness);
456        assert!(result.is_err());
457    }
458
459    #[test]
460    fn copy_constraint_prove_verify() -> Result<(), Error> {
461        // Copy constraint: w0 == w1.
462        let cs = ConstraintSet::empty().with_copy(CopyConstraint::new(Wire::new(0), Wire::new(1)));
463
464        let witness = Witness::new(vec![F101::new(42), F101::new(42)]);
465        let proof = prove(&cs, &witness)?;
466        assert!(verify(&cs, &proof)?);
467        Ok(())
468    }
469
470    #[test]
471    fn copy_constraint_invalid_rejected() {
472        let cs = ConstraintSet::empty().with_copy(CopyConstraint::new(Wire::new(0), Wire::new(1)));
473
474        let witness = Witness::new(vec![F101::new(1), F101::new(2)]);
475        let result = prove(&cs, &witness);
476        assert!(result.is_err());
477    }
478
479    #[test]
480    fn const_gate_prove_verify() -> Result<(), Error> {
481        // Constraint: w0 - 42 = 0.
482        let expr = Expression::Wire(Wire::new(0)) - Expression::Constant(F101::new(42));
483        let cs = ConstraintSet::empty().with_constraint(Constraint::new(expr));
484
485        let witness = Witness::new(vec![F101::new(42)]);
486        let proof = prove(&cs, &witness)?;
487        assert!(verify(&cs, &proof)?);
488        Ok(())
489    }
490}