Skip to content

Commit 722810c

Browse files
feat: integrate zk into spartan (#7)
* checkpoint * checkpoint 1 * remove adapter * debug logs removed * nit * remove option * remove * verify evals at the end * unify transcript except tau * same transcript for tau * move to sumcheck * delete more code * nits * snake * tau check verifier * remove interactive session * remove unwrap or * remove _orig * naming * process round returning chal * move the conversion to reg to setup * move inner/outer sumcheck * call validate * spartan_verifier_circuit * refactor sumcheck * include chal * cleanup --------- Co-authored-by: Srinath Setty <[email protected]>
1 parent bbd6d79 commit 722810c

File tree

7 files changed

+651
-1005
lines changed

7 files changed

+651
-1005
lines changed

src/bellpepper/mod.rs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -66,10 +66,13 @@ pub mod tests {
6666
// ------------------------------------------------------------
6767
// Multi-round circuit test
6868
// ------------------------------------------------------------
69-
use crate::bellpepper::r1cs::{MultiRoundSpartanShape, MultiRoundSpartanWitness};
70-
use crate::bellpepper::shape_cs::ShapeCS;
71-
use crate::traits::circuit::MultiRoundCircuit;
72-
use crate::traits::transcript::TranscriptEngineTrait;
69+
use crate::{
70+
bellpepper::{
71+
r1cs::{MultiRoundSpartanShape, MultiRoundSpartanWitness},
72+
shape_cs::ShapeCS,
73+
},
74+
traits::{circuit::MultiRoundCircuit, transcript::TranscriptEngineTrait},
75+
};
7376

7477
#[derive(Clone)]
7578
pub struct TwoRoundBitsCircuit;

src/bellpepper/r1cs.rs

Lines changed: 5 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ pub trait MultiRoundSpartanWitness<E: Engine> {
8989
is_small: bool,
9090
) -> Result<Self::MultiRoundState, SpartanError>;
9191

92-
/// Process a specific round and update the state.
92+
/// Process a specific round and update the state, returning the challenges generated.
9393
fn process_round<C: MultiRoundCircuit<E>>(
9494
state: &mut Self::MultiRoundState,
9595
s: &SplitMultiRoundR1CSShape<E>,
@@ -98,7 +98,7 @@ pub trait MultiRoundSpartanWitness<E: Engine> {
9898
round_index: usize,
9999
is_small: bool,
100100
transcript: &mut E::TE,
101-
) -> Result<(), SpartanError>;
101+
) -> Result<Vec<E::Scalar>, SpartanError>;
102102

103103
/// Finalize the multi-round witness and return the instance and witness.
104104
fn finalize_multiround_witness<C: MultiRoundCircuit<E>>(
@@ -213,43 +213,6 @@ impl<E: Engine> SpartanShape<E> for ShapeCS<E> {
213213
}
214214
}
215215

216-
impl<E: Engine> SatisfyingAssignment<E> {
217-
/// Helper to process a round and return the concrete challenges generated for that round.
218-
/// This enables interleaving with external computations that need the verifier randomness.
219-
pub fn process_round_returning_challenges<C: MultiRoundCircuit<E>>(
220-
state: &mut MultiRoundState<E>,
221-
s: &SplitMultiRoundR1CSShape<E>,
222-
ck: &CommitmentKey<E>,
223-
circuit: &C,
224-
round_index: usize,
225-
is_small: bool,
226-
transcript: &mut E::TE,
227-
) -> Result<Vec<E::Scalar>, SpartanError> {
228-
// Capture how many challenge rounds we had before
229-
let prev_len = state.challenges_per_round.len();
230-
<SatisfyingAssignment<E> as MultiRoundSpartanWitness<E>>::process_round(
231-
state,
232-
s,
233-
ck,
234-
circuit,
235-
round_index,
236-
is_small,
237-
transcript,
238-
)?;
239-
// Extract newly appended challenges for this round
240-
let new_len = state.challenges_per_round.len();
241-
if new_len == prev_len {
242-
return Ok(vec![]);
243-
}
244-
let last = &state.challenges_per_round[new_len - 1];
245-
let chals = last
246-
.iter()
247-
.map(|a| a.get_value().unwrap_or(E::Scalar::ZERO))
248-
.collect::<Vec<_>>();
249-
Ok(chals)
250-
}
251-
}
252-
253216
pub(crate) fn add_constraint<S: PrimeField>(
254217
X: &mut (
255218
&mut SparseMatrix<S>,
@@ -646,7 +609,7 @@ impl<E: Engine> MultiRoundSpartanWitness<E> for SatisfyingAssignment<E> {
646609
round_index: usize,
647610
is_small: bool,
648611
transcript: &mut E::TE,
649-
) -> Result<(), SpartanError> {
612+
) -> Result<Vec<E::Scalar>, SpartanError> {
650613
if round_index != state.current_round {
651614
return Err(SpartanError::SynthesisError {
652615
reason: format!(
@@ -656,16 +619,6 @@ impl<E: Engine> MultiRoundSpartanWitness<E> for SatisfyingAssignment<E> {
656619
});
657620
}
658621

659-
// If this is the first round, absorb the public values into the transcript.
660-
if round_index == 0 {
661-
let public_values = circuit
662-
.public_values()
663-
.map_err(|e| SpartanError::SynthesisError {
664-
reason: format!("Unable to get public values: {e}"),
665-
})?;
666-
transcript.absorb(b"public_values", &public_values.as_slice());
667-
}
668-
669622
// Absorb commitment from the immediately preceding round (if any)
670623
if let Some(comm) = state.comm_w_per_round.last() {
671624
transcript.absorb(b"comm_w_round", comm);
@@ -721,7 +674,8 @@ impl<E: Engine> MultiRoundSpartanWitness<E> for SatisfyingAssignment<E> {
721674
state.comm_w_per_round.push(comm_w_round);
722675
state.current_round += 1;
723676

724-
Ok(())
677+
// Return the challenges that were generated for this round
678+
Ok(challenges)
725679
}
726680

727681
fn finalize_multiround_witness<C: MultiRoundCircuit<E>>(
@@ -740,7 +694,6 @@ impl<E: Engine> MultiRoundSpartanWitness<E> for SatisfyingAssignment<E> {
740694
});
741695
}
742696

743-
// Get public values
744697
let public_values = circuit
745698
.public_values()
746699
.map_err(|e| SpartanError::SynthesisError {

src/nifs.rs

Lines changed: 38 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,9 @@ where
2929
W1: &RelaxedR1CSWitness<E>,
3030
U2: &R1CSInstance<E>,
3131
W2: &R1CSWitness<E>,
32+
transcript: &mut E::TE,
3233
) -> Result<(Self, (RelaxedR1CSInstance<E>, RelaxedR1CSWitness<E>)), SpartanError> {
33-
// Initialize transcript and absorb both instances.
34-
let mut transcript = E::TE::new(b"nifs");
34+
// Use the caller-provided transcript and absorb both instances.
3535
transcript.absorb(b"U1", U1);
3636
transcript.absorb(b"U2", U2);
3737

@@ -49,31 +49,21 @@ where
4949
Ok((Self { comm_T }, (U, W)))
5050
}
5151

52-
/// Verify folding when the second instance is supplied as a split multi-round
53-
/// R1CS instance. The method first validates the commitments of the split
54-
/// instance and then reproduces the prover’s transcript to derive `r`.
52+
/// Verify folding given a regular instance `U2` that corresponds to the
53+
/// prover's split multi-round instance after conversion.
5554
pub fn verify(
5655
&self,
56+
transcript: &mut E::TE,
5757
U1: &RelaxedR1CSInstance<E>,
58-
U2_split: &crate::r1cs::SplitMultiRoundR1CSInstance<E>,
59-
S_split: &crate::r1cs::SplitMultiRoundR1CSShape<E>,
58+
U2: &R1CSInstance<E>,
6059
) -> Result<RelaxedR1CSInstance<E>, SpartanError> {
61-
// 1. Validate the commitments / structure of the split instance.
62-
let mut tmp = E::TE::new(b"nifs");
63-
U2_split.validate(S_split, &mut tmp)?;
64-
65-
// 2. Convert to a regular instance (the form used by the prover).
66-
let U2_reg = U2_split.to_regular_instance()?;
67-
68-
// 3. Re-run the prover’s transcript schedule to obtain `r`.
69-
let mut transcript = E::TE::new(b"nifs");
60+
// Re-run the prover’s transcript schedule to obtain `r` using caller transcript.
7061
transcript.absorb(b"U1", U1);
71-
transcript.absorb(b"U2", &U2_reg);
62+
transcript.absorb(b"U2", U2);
7263
transcript.absorb(b"comm_T", &self.comm_T);
7364
let r = transcript.squeeze(b"r")?;
7465

75-
// 4. Return the folded instance.
76-
Ok(U1.fold(&U2_reg, &self.comm_T, &r))
66+
Ok(U1.fold(U2, &self.comm_T, &r))
7767
}
7868
}
7969

@@ -147,10 +137,22 @@ mod tests {
147137
let (running_U, running_W) = S_reg.sample_random_instance_witness(&ck).unwrap();
148138

149139
// Prove & verify
150-
let (proof, (folded_U, folded_W)) =
151-
NIFS::<E>::prove(&ck, &S_reg, &running_U, &running_W, &inst_reg, &wit_reg).unwrap();
140+
let mut transcript = <E as Engine>::TE::new(b"nifs");
141+
let (proof, (folded_U, folded_W)) = NIFS::<E>::prove(
142+
&ck,
143+
&S_reg,
144+
&running_U,
145+
&running_W,
146+
&inst_reg,
147+
&wit_reg,
148+
&mut transcript,
149+
)
150+
.unwrap();
152151

153-
let verified_U = proof.verify(&running_U, &inst_mr, &shape_mr).unwrap();
152+
// Validation now happens at callsite; here we provide a regular U2 and transcript
153+
let mut transcript = <E as Engine>::TE::new(b"nifs");
154+
let U2_reg = inst_mr.to_regular_instance().unwrap();
155+
let verified_U = proof.verify(&mut transcript, &running_U, &U2_reg).unwrap();
154156
assert_eq!(verified_U, folded_U);
155157
assert!(S_reg.is_sat_relaxed(&ck, &folded_U, &folded_W).is_ok());
156158
}
@@ -215,10 +217,21 @@ mod tests {
215217
let (running_U, running_W) = S_reg.sample_random_instance_witness(&ck).unwrap();
216218

217219
// NIFS prove + verify
218-
let (proof, (folded_U, folded_W)) =
219-
NIFS::<E>::prove(&ck, &S_reg, &running_U, &running_W, &inst_reg, &wit_reg).unwrap();
220+
let mut transcript_nifs = <E as Engine>::TE::new(b"nifs");
221+
let (proof, (folded_U, folded_W)) = NIFS::<E>::prove(
222+
&ck,
223+
&S_reg,
224+
&running_U,
225+
&running_W,
226+
&inst_reg,
227+
&wit_reg,
228+
&mut transcript_nifs,
229+
)
230+
.unwrap();
220231

221-
let verified_U = proof.verify(&running_U, &inst_split, &shape_mr).unwrap();
232+
let mut transcript = <E as Engine>::TE::new(b"nifs");
233+
let U2_reg = inst_split.to_regular_instance().unwrap();
234+
let verified_U = proof.verify(&mut transcript, &running_U, &U2_reg).unwrap();
222235
assert_eq!(verified_U, folded_U);
223236
assert!(S_reg.is_sat_relaxed(&ck, &folded_U, &folded_W).is_ok());
224237
}

src/r1cs/mod.rs

Lines changed: 12 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ pub struct R1CSInstance<E: Engine> {
7676

7777
/// A type that holds a witness for a given Relaxed R1CS instance
7878
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
79+
#[serde(bound = "")]
7980
pub struct RelaxedR1CSWitness<E: Engine> {
8081
pub(crate) W: Vec<E::Scalar>,
8182
pub(crate) r_W: Blind<E>,
@@ -1009,9 +1010,6 @@ impl<E: Engine> SplitR1CSInstance<E> {
10091010
S: &SplitR1CSShape<E>,
10101011
transcript: &mut E::TE,
10111012
) -> Result<(), SpartanError> {
1012-
// absorb the public IO into the transcript
1013-
transcript.absorb(b"public_values", &self.public_values.as_slice());
1014-
10151013
if S.num_shared > 0 {
10161014
if let Some(comm) = &self.comm_W_shared {
10171015
E::PCS::check_partial(comm, S.num_shared)?;
@@ -1258,16 +1256,18 @@ impl<E: Engine> SplitMultiRoundR1CSInstance<E> {
12581256
s: &SplitMultiRoundR1CSShape<E>,
12591257
transcript: &mut E::TE,
12601258
) -> Result<(), SpartanError> {
1261-
// absorb the public IO into the transcript
1262-
transcript.absorb(b"public_values", &self.public_values.as_slice());
1263-
1264-
// Process each round
1259+
// Process each round, absorbing the previous round's commitment before deriving this round's challenges
12651260
for round in 0..s.num_rounds {
1266-
// Generate and validate challenges for this round BEFORE absorbing the current round's commitment.
1267-
// This mirrors the order used by the prover, where challenges for round `r` are derived
1268-
// after absorbing the commitment from the *previous* round (if any), but **before** the
1269-
// commitment of the current round is absorbed. This ensures that the commitment of round `r`
1270-
// cannot influence the challenges of the same round.
1261+
if round > 0 {
1262+
// Absorb commitment of previous round to influence current round's challenges
1263+
E::PCS::check_partial(
1264+
&self.comm_w_per_round[round - 1],
1265+
s.num_vars_per_round[round - 1],
1266+
)?;
1267+
transcript.absorb(b"comm_w_round", &self.comm_w_per_round[round - 1]);
1268+
}
1269+
1270+
// Derive and validate challenges for this round
12711271
let expected_challenges = (0..s.num_challenges_per_round[round])
12721272
.map(|_| transcript.squeeze(b"challenge"))
12731273
.collect::<Result<Vec<E::Scalar>, SpartanError>>()?;
@@ -1277,11 +1277,6 @@ impl<E: Engine> SplitMultiRoundR1CSInstance<E> {
12771277
reason: format!("Challenges for round {round} do not match"),
12781278
});
12791279
}
1280-
1281-
// After validating challenges, absorb the commitment for the current round so that it
1282-
// affects the transcript state for the *next* round's challenges.
1283-
E::PCS::check_partial(&self.comm_w_per_round[round], s.num_vars_per_round[round])?;
1284-
transcript.absorb(b"comm_w_round", &self.comm_w_per_round[round]);
12851280
}
12861281

12871282
Ok(())

0 commit comments

Comments
 (0)