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