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