Skip to content

Commit 75ab49a

Browse files
authored
chore: or proofs with new API (#73)
1 parent 2c826bb commit 75ab49a

File tree

6 files changed

+112
-87
lines changed

6 files changed

+112
-87
lines changed

examples/simple_composition.rs

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ use sigma_rs::{
1010
errors::Error,
1111
LinearRelation, Nizk,
1212
};
13-
use subtle::CtOption;
1413

1514
type G = RistrettoPoint;
1615
type ProofResult<T> = Result<T, Error>;
@@ -56,11 +55,8 @@ fn prove(P1: G, x2: Scalar, H: G) -> ProofResult<Vec<u8>> {
5655
let instance = create_relation(P1, P2, Q, H);
5756
// Create OR witness with branch 1 being the real one (index 1)
5857
let witness = ComposedWitness::Or(vec![
59-
CtOption::new(
60-
ComposedWitness::Simple(vec![Scalar::from(0u64)]),
61-
0u8.into(),
62-
), // dummy for branch 0
63-
CtOption::new(ComposedWitness::Simple(vec![x2]), 1u8.into()), // real witness for branch 1
58+
ComposedWitness::Simple(vec![Scalar::from(0u64)]),
59+
ComposedWitness::Simple(vec![x2]),
6460
]);
6561
let nizk = Nizk::<_, Shake128DuplexSponge<G>>::new(b"or_proof_example", instance);
6662

src/composition.rs

Lines changed: 78 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@ use ff::{Field, PrimeField};
2222
use group::prime::PrimeGroup;
2323
use sha3::Digest;
2424
use sha3::Sha3_256;
25-
use subtle::CtOption;
25+
use subtle::Choice;
26+
use subtle::ConditionallySelectable;
27+
use subtle::ConstantTimeEq;
2628

2729
use crate::{
2830
codec::Shake128DuplexSponge,
@@ -71,17 +73,15 @@ pub enum ComposedCommitment<G: PrimeGroup> {
7173
}
7274

7375
// Structure representing the ProverState type of Protocol as SigmaProtocol
74-
pub enum ComposedProverState<G: PrimeGroup> {
76+
pub enum ComposedProverState<G: PrimeGroup + ConstantTimeEq> {
7577
Simple(<SchnorrProof<G> as SigmaProtocol>::ProverState),
7678
And(Vec<ComposedProverState<G>>),
7779
Or(ComposedOrProverState<G>),
7880
}
7981

80-
type ComposedOrProverState<G> = (
81-
Vec<Option<ComposedProverState<G>>>,
82-
Vec<Option<ComposedChallenge<G>>>,
83-
Vec<Option<ComposedResponse<G>>>,
84-
);
82+
struct ComposedOrProverState<G: PrimeGroup + ConstantTimeEq> {
83+
prover_states: Vec<(Choice, ComposedProverState<G>, ComposedChallenge<G>, ComposedResponse<G>)>,
84+
}
8585

8686
// Structure representing the Response type of Protocol as SigmaProtocol
8787
#[derive(Clone)]
@@ -96,7 +96,7 @@ pub enum ComposedResponse<G: PrimeGroup> {
9696
pub enum ComposedWitness<G: PrimeGroup> {
9797
Simple(<SchnorrProof<G> as SigmaProtocol>::Witness),
9898
And(Vec<ComposedWitness<G>>),
99-
Or(Vec<CtOption<ComposedWitness<G>>>),
99+
Or(Vec<ComposedWitness<G>>),
100100
}
101101

102102
type ComposedChallenge<G> = <SchnorrProof<G> as SigmaProtocol>::Challenge;
@@ -105,7 +105,30 @@ const fn composed_challenge_size<G: PrimeGroup>() -> usize {
105105
(G::Scalar::NUM_BITS as usize + 7) / 8
106106
}
107107

108-
impl<G: PrimeGroup> ComposedRelation<G> {
108+
109+
impl<G: PrimeGroup + ConstantTimeEq> ComposedRelation<G> {
110+
fn is_witness_valid(&self, witness: &ComposedWitness<G>) -> Choice {
111+
let validity_bit = Choice::from(0);
112+
match (self, witness) {
113+
(ComposedRelation::Simple(instance), ComposedWitness::Simple(witness)) => {
114+
instance.0.is_witness_valid(witness)
115+
}
116+
(ComposedRelation::And(instances), ComposedWitness::And(witnesses)) => instances
117+
.iter()
118+
.zip(witnesses)
119+
.fold(Choice::from(0), |bit, (instance, witness)| {
120+
bit & instance.is_witness_valid(witness)
121+
}),
122+
(ComposedRelation::Or(instances), ComposedWitness::Or(witnesses)) => instances
123+
.iter()
124+
.zip(witnesses)
125+
.fold(Choice::from(0), |bit, (instance, witness)| {
126+
bit | instance.is_witness_valid(witness)
127+
}),
128+
_ => unreachable!(),
129+
};
130+
validity_bit
131+
}
109132
fn prover_commit_simple(
110133
protocol: &SchnorrProof<G>,
111134
witness: &<SchnorrProof<G> as SigmaProtocol>::Witness,
@@ -173,45 +196,43 @@ impl<G: PrimeGroup> ComposedRelation<G> {
173196

174197
fn prover_commit_or(
175198
instances: &[ComposedRelation<G>],
176-
witnesses: &[CtOption<ComposedWitness<G>>],
199+
witnesses: &[ComposedWitness<G>],
177200
rng: &mut (impl rand::Rng + rand::CryptoRng),
178201
) -> Result<(ComposedCommitment<G>, ComposedProverState<G>), Error> {
179202
if instances.len() != witnesses.len() {
180203
return Err(Error::InvalidInstanceWitnessPair);
181204
}
182205

183-
let mut simulated_challenges = Vec::new();
184-
let mut simulated_responses = Vec::new();
185-
let mut commitments = Vec::<ComposedCommitment<G>>::with_capacity(instances.len());
206+
let mut commitments = Vec::new();
186207
let mut prover_states = Vec::new();
187208

188-
for (i, witness) in witnesses.iter().enumerate() {
189-
// let (simulated_commitment, simulated_challenge, simulated_response) = instances[i].simulate_transcript(rng)?;
190-
let witness = witness.clone().into_option();
191-
match witness {
192-
Some(w) => {
193-
let (commitment, prover_state) = instances[i].prover_commit(&w, rng)?;
194-
commitments.push(commitment);
195-
prover_states.push(Some(prover_state));
196-
simulated_challenges.push(None);
197-
simulated_responses.push(None);
198-
}
199-
None => {
200-
let (simulated_commitment, simulated_challenge, simulated_response) =
201-
instances[i].simulate_transcript(rng)?;
202-
commitments.push(simulated_commitment);
203-
prover_states.push(None);
204-
simulated_challenges.push(Some(simulated_challenge));
205-
simulated_responses.push(Some(simulated_response));
206-
}
207-
}
209+
for (i, w) in witnesses.iter().enumerate() {
210+
let (commitment, prover_state) = instances[i].prover_commit(&w, rng)?;
211+
let (simulated_commitment, simulated_challenge, simulated_response) =
212+
instances[i].simulate_transcript(rng)?;
213+
214+
let valid_witness = instances[i].is_witness_valid(&w);
215+
commitments.push(if valid_witness.unwrap_u8() == 1 {commitment } else { simulated_commitment.clone()} );
216+
prover_states.push((valid_witness, prover_state, simulated_challenge, simulated_response));
217+
}
218+
// check that we have only one witness set
219+
let witnesses_found = prover_states
220+
.iter()
221+
.map(|x| x.0.unwrap_u8() as usize)
222+
.sum::<usize>();
223+
let prover_state =
224+
ComposedOrProverState {
225+
prover_states,
226+
};
227+
228+
if witnesses_found > 1 {
229+
return Err(Error::InvalidInstanceWitnessPair);
230+
} else {
231+
Ok((
232+
ComposedCommitment::Or(commitments),
233+
ComposedProverState::Or(prover_state),
234+
))
208235
}
209-
let prover_state: ComposedOrProverState<G> =
210-
(prover_states, simulated_challenges, simulated_responses);
211-
Ok((
212-
ComposedCommitment::Or(commitments),
213-
ComposedProverState::Or(prover_state),
214-
))
215236
}
216237

217238
fn prover_response_or(
@@ -222,34 +243,30 @@ impl<G: PrimeGroup> ComposedRelation<G> {
222243
let mut result_challenges = Vec::with_capacity(instances.len());
223244
let mut result_responses = Vec::with_capacity(instances.len());
224245

225-
// Calculate the real challenge by subtracting all simulated challenges
226-
let (child_states, simulated_challenges, simulated_responses) = prover_state;
246+
let ComposedOrProverState { prover_states } = prover_state;
227247

228-
let real_challenge = challenge - simulated_challenges.iter().flatten().sum::<G::Scalar>();
248+
let mut witness_challenge = challenge;
249+
for (valid_witness, _prover_state, simulated_challenge, _simulated_response) in &prover_states {
250+
let c = G::Scalar::conditional_select(&G::Scalar::ZERO, &simulated_challenge, *valid_witness);
251+
witness_challenge -= c;
252+
}
253+
for (instance, (valid_witness, prover_state, simulated_challenge, simulated_response)) in instances.iter().zip(prover_states) {
254+
let challenge_i = G::Scalar::conditional_select(&witness_challenge, &simulated_challenge, valid_witness);
229255

230-
let it = instances
231-
.iter()
232-
.zip(child_states)
233-
.zip(simulated_challenges)
234-
.zip(simulated_responses);
235-
for (((i, prover_state), simulated_challenge), simulated_response) in it {
236-
if let Some(state) = prover_state {
237-
// Real case: compute response with real challenge
238-
let response = i.prover_response(state, &real_challenge)?;
239-
result_challenges.push(real_challenge);
240-
result_responses.push(response);
241-
} else {
242-
result_challenges.push(simulated_challenge.unwrap());
243-
result_responses.push(simulated_response.unwrap());
244-
}
256+
let real_response = instance.prover_response(prover_state, &challenge_i)?;
257+
258+
// let response_i = ComposedResponse::conditional_select(&real_response, &simulated_response, *witness_location);
259+
let response_i = if valid_witness.unwrap_u8() == 1 { real_response } else { simulated_response };
260+
result_challenges.push(challenge_i);
261+
result_responses.push(response_i);
245262
}
246-
result_challenges.pop();
247263

264+
result_challenges.pop();
248265
Ok(ComposedResponse::Or(result_challenges, result_responses))
249266
}
250267
}
251268

252-
impl<G: PrimeGroup> SigmaProtocol for ComposedRelation<G> {
269+
impl<G: PrimeGroup + ConstantTimeEq> SigmaProtocol for ComposedRelation<G> {
253270
type Commitment = ComposedCommitment<G>;
254271
type ProverState = ComposedProverState<G>;
255272
type Response = ComposedResponse<G>;
@@ -501,7 +518,7 @@ impl<G: PrimeGroup> SigmaProtocol for ComposedRelation<G> {
501518
}
502519
}
503520

504-
impl<G: PrimeGroup> SigmaProtocolSimulator for ComposedRelation<G> {
521+
impl<G: PrimeGroup + ConstantTimeEq> SigmaProtocolSimulator for ComposedRelation<G> {
505522
fn simulate_commitment(
506523
&self,
507524
challenge: &Self::Challenge,
@@ -606,7 +623,7 @@ impl<G: PrimeGroup> SigmaProtocolSimulator for ComposedRelation<G> {
606623
}
607624
}
608625

609-
impl<G: PrimeGroup> ComposedRelation<G> {
626+
impl<G: PrimeGroup + ConstantTimeEq> ComposedRelation<G> {
610627
/// Convert this Protocol into a non-interactive zero-knowledge proof
611628
/// using the Shake128DuplexSponge codec and a specified session identifier.
612629
///

src/linear_relation/canonical.rs

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@ use std::marker::PhantomData;
44

55
use ff::Field;
66
use group::prime::PrimeGroup;
7+
use subtle::{Choice, ConstantTimeEq};
78

89
use super::{GroupMap, GroupVar, LinearCombination, LinearRelation, ScalarTerm, ScalarVar};
910
use crate::errors::{Error, InvalidInstance};
11+
use crate::linear_relation::msm_pr;
1012

1113
/// A normalized form of the [`LinearRelation`], which is used for serialization into the transcript.
1214
///
@@ -48,17 +50,19 @@ impl<G: PrimeGroup> CanonicalLinearRelation<G> {
4850
}
4951

5052
/// Evaluate the canonical linear relation with the provided scalars
51-
pub fn evaluate(&self, scalars: &[G::Scalar]) -> Result<Vec<G>, Error> {
53+
pub fn evaluate(&self, scalars: &[G::Scalar]) -> Vec<G> {
5254
self.linear_combinations
5355
.iter()
54-
.map(|constraint| {
55-
let mut result = G::identity();
56-
for (scalar_var, group_var) in constraint {
57-
let scalar_val = scalars[scalar_var.index()];
58-
let group_val = self.group_elements.get(*group_var)?;
59-
result += group_val * scalar_val;
60-
}
61-
Ok(result)
56+
.map(|lc| {
57+
let scalars = lc
58+
.iter()
59+
.map(|(scalar_var, _)| scalars[scalar_var.index()])
60+
.collect::<Vec<_>>();
61+
let bases = lc
62+
.iter()
63+
.map(|(_, group_var)| self.group_elements.get(*group_var).unwrap())
64+
.collect::<Vec<_>>();
65+
msm_pr(&scalars, &bases)
6266
})
6367
.collect()
6468
}
@@ -430,3 +434,13 @@ impl<G: PrimeGroup> TryFrom<&LinearRelation<G>> for CanonicalLinearRelation<G> {
430434
Ok(canonical)
431435
}
432436
}
437+
438+
impl<G: PrimeGroup + ConstantTimeEq> CanonicalLinearRelation<G> {
439+
pub fn is_witness_valid(&self, witness: &[G::Scalar]) -> Choice {
440+
let got = self.evaluate(witness);
441+
self.image
442+
.iter()
443+
.zip(got)
444+
.fold(Choice::from(1), |acc, (lhs, rhs)| acc & lhs.ct_eq(&rhs))
445+
}
446+
}

src/schnorr_protocol.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ impl<G: PrimeGroup> SchnorrProof<G> {
6363
return Err(Error::InvalidInstanceWitnessPair);
6464
}
6565

66-
let commitment = self.0.evaluate(nonces)?;
66+
let commitment = self.0.evaluate(nonces);
6767
let prover_state = ProverState(nonces.to_vec(), witness.to_vec());
6868
Ok((commitment, prover_state))
6969
}
@@ -176,7 +176,7 @@ impl<G: PrimeGroup> SigmaProtocol for SchnorrProof<G> {
176176
return Err(Error::InvalidInstanceWitnessPair);
177177
}
178178

179-
let lhs = self.0.evaluate(response)?;
179+
let lhs = self.0.evaluate(response);
180180
let mut rhs = Vec::new();
181181
for (i, g) in commitment.iter().enumerate() {
182182
rhs.push(self.0.image[i] * challenge + g);
@@ -349,7 +349,7 @@ where
349349
return Err(Error::InvalidInstanceWitnessPair);
350350
}
351351

352-
let response_image = self.0.evaluate(response)?;
352+
let response_image = self.0.evaluate(response);
353353
let image = &self.0.image;
354354

355355
let commitment = response_image

src/tests/test_composition.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
use curve25519_dalek::ristretto::RistrettoPoint;
22
use group::Group;
33
use rand::rngs::OsRng;
4-
use subtle::CtOption;
54

65
use super::test_relations::*;
76
use crate::composition::{ComposedRelation, ComposedWitness};
@@ -38,8 +37,8 @@ fn test_composition_correctness() {
3837
ComposedRelation::Simple(SchnorrProof(relation2)),
3938
]);
4039
let or_witness1 = ComposedWitness::Or(vec![
41-
CtOption::new(ComposedWitness::Simple(witness1), 1u8.into()),
42-
CtOption::new(ComposedWitness::Simple(wrong_witness2), 0u8.into()),
40+
ComposedWitness::Simple(witness1),
41+
ComposedWitness::Simple(wrong_witness2),
4342
]);
4443

4544
let simple_protocol1 = ComposedRelation::Simple(SchnorrProof(relation3));

src/tests/test_validation_criteria.rs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,6 @@ mod proof_validation {
210210
use bls12_381::{G1Projective as G, Scalar};
211211
use ff::Field;
212212
use rand::{thread_rng, RngCore};
213-
use subtle::CtOption;
214213

215214
type TestNizk = Nizk<SchnorrProof<G>, KeccakByteSchnorrCodec<G>>;
216215

@@ -419,8 +418,8 @@ mod proof_validation {
419418

420419
// Create a correct witness for branch 1 (C = y*B)
421420
let witness_correct = ComposedWitness::Or(vec![
422-
CtOption::new(ComposedWitness::Simple(vec![x]), 0u8.into()), // branch 0 not used
423-
CtOption::new(ComposedWitness::Simple(vec![y]), 1u8.into()), // branch 1 is real
421+
ComposedWitness::Simple(vec![x]),
422+
ComposedWitness::Simple(vec![y]),
424423
]);
425424

426425
// This should succeed since branch 1 is correct
@@ -433,8 +432,8 @@ mod proof_validation {
433432
// Now test with wrong witness: using branch 0 when it's not satisfied
434433
// Branch 0 requires C = x*A, but C = y*B and A ≠ B, so x would need to be y/42
435434
let witness_wrong = ComposedWitness::Or(vec![
436-
CtOption::new(ComposedWitness::Simple(vec![x]), 1u8.into()), // branch 0 is real (but wrong!)
437-
CtOption::new(ComposedWitness::Simple(vec![y]), 0u8.into()), // branch 1 not used
435+
ComposedWitness::Simple(vec![x]),
436+
ComposedWitness::Simple(vec![y]),
438437
]);
439438
let proof_result = nizk.prove_batchable(&witness_wrong, &mut rng);
440439

0 commit comments

Comments
 (0)