Skip to content

Commit 2e2b3aa

Browse files
authored
Remove SchnorrProof in favor of CanonicalLinearRelation (#72)
1 parent 86fbae1 commit 2e2b3aa

File tree

8 files changed

+85
-132
lines changed

8 files changed

+85
-132
lines changed

src/composition.rs

Lines changed: 20 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
//! # Protocol Composition with AND/OR Logic
22
//!
3-
//! This module defines the [`ComposedRelation`] enum, which generalizes the [`SchnorrProof`]
3+
//! This module defines the [`ComposedRelation`] enum, which generalizes the [`CanonicalLinearRelation`]
44
//! by enabling compositional logic between multiple proof instances.
55
//!
66
//! Specifically, it supports:
@@ -30,51 +30,50 @@ use crate::{
3030
codec::Shake128DuplexSponge,
3131
errors::Error,
3232
fiat_shamir::Nizk,
33-
linear_relation::LinearRelation,
34-
schnorr_protocol::SchnorrProof,
33+
linear_relation::{CanonicalLinearRelation, LinearRelation},
3534
serialization::{deserialize_scalars, serialize_scalars},
3635
traits::{SigmaProtocol, SigmaProtocolSimulator},
3736
};
3837

39-
/// A protocol proving knowledge of a witness for a composition of SchnorrProof's.
38+
/// A protocol proving knowledge of a witness for a composition of linear relations.
4039
///
41-
/// This implementation generalizes [`SchnorrProof`] by using AND/OR links.
40+
/// This implementation generalizes [`CanonicalLinearRelation`] by using AND/OR links.
4241
///
4342
/// # Type Parameters
4443
/// - `G`: A cryptographic group implementing [`group::Group`] and [`group::GroupEncoding`].
4544
#[derive(Clone)]
4645
pub enum ComposedRelation<G: PrimeGroup> {
47-
Simple(SchnorrProof<G>),
46+
Simple(CanonicalLinearRelation<G>),
4847
And(Vec<ComposedRelation<G>>),
4948
Or(Vec<ComposedRelation<G>>),
5049
}
5150

52-
impl<G: PrimeGroup> From<SchnorrProof<G>> for ComposedRelation<G> {
53-
fn from(value: SchnorrProof<G>) -> Self {
51+
impl<G: PrimeGroup> From<CanonicalLinearRelation<G>> for ComposedRelation<G> {
52+
fn from(value: CanonicalLinearRelation<G>) -> Self {
5453
ComposedRelation::Simple(value)
5554
}
5655
}
5756

5857
impl<G: PrimeGroup> From<LinearRelation<G>> for ComposedRelation<G> {
5958
fn from(value: LinearRelation<G>) -> Self {
6059
Self::Simple(
61-
SchnorrProof::try_from(value)
62-
.expect("Failed to convert LinearRelation to SchnorrProof"),
60+
CanonicalLinearRelation::try_from(value)
61+
.expect("Failed to convert LinearRelation to CanonicalLinearRelation"),
6362
)
6463
}
6564
}
6665

6766
// Structure representing the Commitment type of Protocol as SigmaProtocol
6867
#[derive(Clone)]
6968
pub enum ComposedCommitment<G: PrimeGroup> {
70-
Simple(<SchnorrProof<G> as SigmaProtocol>::Commitment),
69+
Simple(<CanonicalLinearRelation<G> as SigmaProtocol>::Commitment),
7170
And(Vec<ComposedCommitment<G>>),
7271
Or(Vec<ComposedCommitment<G>>),
7372
}
7473

7574
// Structure representing the ProverState type of Protocol as SigmaProtocol
7675
pub enum ComposedProverState<G: PrimeGroup + ConstantTimeEq> {
77-
Simple(<SchnorrProof<G> as SigmaProtocol>::ProverState),
76+
Simple(<CanonicalLinearRelation<G> as SigmaProtocol>::ProverState),
7877
And(Vec<ComposedProverState<G>>),
7978
Or(ComposedOrProverState<G>),
8079
}
@@ -90,20 +89,20 @@ pub struct ComposedOrProverStateEntry<G: PrimeGroup + ConstantTimeEq>(
9089
// Structure representing the Response type of Protocol as SigmaProtocol
9190
#[derive(Clone)]
9291
pub enum ComposedResponse<G: PrimeGroup> {
93-
Simple(<SchnorrProof<G> as SigmaProtocol>::Response),
92+
Simple(<CanonicalLinearRelation<G> as SigmaProtocol>::Response),
9493
And(Vec<ComposedResponse<G>>),
9594
Or(Vec<ComposedChallenge<G>>, Vec<ComposedResponse<G>>),
9695
}
9796

9897
// Structure representing the Witness type of Protocol as SigmaProtocol
9998
#[derive(Clone)]
10099
pub enum ComposedWitness<G: PrimeGroup> {
101-
Simple(<SchnorrProof<G> as SigmaProtocol>::Witness),
100+
Simple(<CanonicalLinearRelation<G> as SigmaProtocol>::Witness),
102101
And(Vec<ComposedWitness<G>>),
103102
Or(Vec<ComposedWitness<G>>),
104103
}
105104

106-
type ComposedChallenge<G> = <SchnorrProof<G> as SigmaProtocol>::Challenge;
105+
type ComposedChallenge<G> = <CanonicalLinearRelation<G> as SigmaProtocol>::Challenge;
107106

108107
const fn composed_challenge_size<G: PrimeGroup>() -> usize {
109108
(G::Scalar::NUM_BITS as usize).div_ceil(8)
@@ -113,7 +112,7 @@ impl<G: PrimeGroup + ConstantTimeEq> ComposedRelation<G> {
113112
fn is_witness_valid(&self, witness: &ComposedWitness<G>) -> Choice {
114113
match (self, witness) {
115114
(ComposedRelation::Simple(instance), ComposedWitness::Simple(witness)) => {
116-
instance.0.is_witness_valid(witness)
115+
instance.is_witness_valid(witness)
117116
}
118117
(ComposedRelation::And(instances), ComposedWitness::And(witnesses)) => instances
119118
.iter()
@@ -132,8 +131,8 @@ impl<G: PrimeGroup + ConstantTimeEq> ComposedRelation<G> {
132131
}
133132

134133
fn prover_commit_simple(
135-
protocol: &SchnorrProof<G>,
136-
witness: &<SchnorrProof<G> as SigmaProtocol>::Witness,
134+
protocol: &CanonicalLinearRelation<G>,
135+
witness: &<CanonicalLinearRelation<G> as SigmaProtocol>::Witness,
137136
rng: &mut (impl rand::Rng + rand::CryptoRng),
138137
) -> Result<(ComposedCommitment<G>, ComposedProverState<G>), Error> {
139138
protocol.prover_commit(witness, rng).map(|(c, s)| {
@@ -145,9 +144,9 @@ impl<G: PrimeGroup + ConstantTimeEq> ComposedRelation<G> {
145144
}
146145

147146
fn prover_response_simple(
148-
instance: &SchnorrProof<G>,
149-
state: <SchnorrProof<G> as SigmaProtocol>::ProverState,
150-
challenge: &<SchnorrProof<G> as SigmaProtocol>::Challenge,
147+
instance: &CanonicalLinearRelation<G>,
148+
state: <CanonicalLinearRelation<G> as SigmaProtocol>::ProverState,
149+
challenge: &<CanonicalLinearRelation<G> as SigmaProtocol>::Challenge,
151150
) -> Result<ComposedResponse<G>, Error> {
152151
instance
153152
.prover_response(state, challenge)

src/linear_relation/canonical.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,14 @@ impl<G: PrimeGroup> CanonicalLinearRelation<G> {
5050
}
5151

5252
/// Evaluate the canonical linear relation with the provided scalars
53+
///
54+
/// This returns a list of image points produced by evaluating each linear combination in the
55+
/// relation. The order of the returned list matches the order of [`Self::linear_combinations`].
56+
///
57+
/// # Panic
58+
///
59+
/// Panics if the number of scalars given is less than the number of scalar variables in this
60+
/// linear relation
5361
pub fn evaluate(&self, scalars: &[G::Scalar]) -> Vec<G> {
5462
self.linear_combinations
5563
.iter()
@@ -376,6 +384,14 @@ impl<G: PrimeGroup> CanonicalLinearRelation<G> {
376384
}
377385
}
378386

387+
impl<G: PrimeGroup> TryFrom<LinearRelation<G>> for CanonicalLinearRelation<G> {
388+
type Error = InvalidInstance;
389+
390+
fn try_from(value: LinearRelation<G>) -> Result<Self, Self::Error> {
391+
Self::try_from(&value)
392+
}
393+
}
394+
379395
impl<G: PrimeGroup> TryFrom<&LinearRelation<G>> for CanonicalLinearRelation<G> {
380396
type Error = InvalidInstance;
381397

src/linear_relation/mod.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ use group::prime::PrimeGroup;
1616

1717
use crate::codec::Shake128DuplexSponge;
1818
use crate::errors::{Error, InvalidInstance};
19-
use crate::schnorr_protocol::SchnorrProof;
2019
use crate::Nizk;
2120

2221
/// Implementations of conversion operations such as From and FromIterator for var and term types.
@@ -539,7 +538,7 @@ impl<G: PrimeGroup> LinearRelation<G> {
539538
pub fn into_nizk(
540539
self,
541540
session_identifier: &[u8],
542-
) -> Result<Nizk<SchnorrProof<G>, Shake128DuplexSponge<G>>, InvalidInstance> {
541+
) -> Result<Nizk<CanonicalLinearRelation<G>, Shake128DuplexSponge<G>>, InvalidInstance> {
543542
Ok(Nizk::new(session_identifier, self.try_into()?))
544543
}
545544
}

src/schnorr_protocol.rs

Lines changed: 24 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
//! a Sigma protocol proving different types of discrete logarithm relations (eg. Schnorr, Pedersen's commitments)
55
//! through a group morphism abstraction (see [Maurer09](https://crypto-test.ethz.ch/publications/files/Maurer09.pdf)).
66
7-
use crate::errors::{Error, InvalidInstance};
8-
use crate::linear_relation::{CanonicalLinearRelation, LinearRelation};
7+
use crate::errors::Error;
8+
use crate::linear_relation::CanonicalLinearRelation;
99
use crate::{
1010
serialization::{
1111
deserialize_elements, deserialize_scalars, serialize_elements, serialize_scalars,
@@ -17,70 +17,9 @@ use ff::Field;
1717
use group::prime::PrimeGroup;
1818
use rand::{CryptoRng, Rng, RngCore};
1919

20-
/// A Schnorr protocol proving knowledge of a witness for a linear group relation.
21-
///
22-
/// This implementation generalizes Schnorr’s discrete logarithm proof by using
23-
/// a [`LinearRelation`], representing an abstract linear relation over the group.
24-
///
25-
/// # Type Parameters
26-
/// - `G`: A [`PrimeGroup`] instance.
27-
#[derive(Clone, Default, Debug)]
28-
pub struct SchnorrProof<G: PrimeGroup>(pub CanonicalLinearRelation<G>);
29-
30-
pub struct ProverState<G: PrimeGroup>(Vec<G::Scalar>, Vec<G::Scalar>);
31-
32-
impl<G: PrimeGroup> SchnorrProof<G> {
33-
pub fn witness_length(&self) -> usize {
34-
self.0.num_scalars
35-
}
36-
37-
pub fn commitment_length(&self) -> usize {
38-
self.0.linear_combinations.len()
39-
}
40-
41-
/// Internal method to commit using provided nonces (for deterministic testing)
42-
pub fn commit_with_nonces(
43-
&self,
44-
witness: &[G::Scalar],
45-
nonces: &[G::Scalar],
46-
) -> Result<(Vec<G>, ProverState<G>), Error> {
47-
if witness.len() != self.witness_length() {
48-
return Err(Error::InvalidInstanceWitnessPair);
49-
}
50-
if nonces.len() != self.witness_length() {
51-
return Err(Error::InvalidInstanceWitnessPair);
52-
}
53-
54-
// If the image is the identity, then the relation must be
55-
// trivial, or else the proof will be unsound
56-
if self
57-
.0
58-
.image
59-
.iter()
60-
.zip(self.0.linear_combinations.iter())
61-
.any(|(&x, c)| x == G::identity() && !c.is_empty())
62-
{
63-
return Err(Error::InvalidInstanceWitnessPair);
64-
}
65-
66-
let commitment = self.0.evaluate(nonces);
67-
let prover_state = ProverState(nonces.to_vec(), witness.to_vec());
68-
Ok((commitment, prover_state))
69-
}
70-
}
71-
72-
impl<G: PrimeGroup> TryFrom<LinearRelation<G>> for SchnorrProof<G> {
73-
type Error = InvalidInstance;
74-
75-
fn try_from(linear_relation: LinearRelation<G>) -> Result<Self, Self::Error> {
76-
let canonical_linear_relation = CanonicalLinearRelation::try_from(&linear_relation)?;
77-
Ok(Self(canonical_linear_relation))
78-
}
79-
}
80-
81-
impl<G: PrimeGroup> SigmaProtocol for SchnorrProof<G> {
20+
impl<G: PrimeGroup> SigmaProtocol for CanonicalLinearRelation<G> {
8221
type Commitment = Vec<G>;
83-
type ProverState = ProverState<G>;
22+
type ProverState = (Vec<G::Scalar>, Vec<G::Scalar>);
8423
type Response = Vec<G::Scalar>;
8524
type Witness = Vec<G::Scalar>;
8625
type Challenge = G::Scalar;
@@ -103,26 +42,29 @@ impl<G: PrimeGroup> SigmaProtocol for SchnorrProof<G> {
10342
witness: &Self::Witness,
10443
rng: &mut (impl RngCore + CryptoRng),
10544
) -> Result<(Self::Commitment, Self::ProverState), Error> {
106-
if witness.len() != self.witness_length() {
45+
if witness.len() != self.num_scalars {
10746
return Err(Error::InvalidInstanceWitnessPair);
10847
}
10948

49+
// TODO: Check this when constructing the CanonicalLinearRelation instead of here.
11050
// If the image is the identity, then the relation must be
11151
// trivial, or else the proof will be unsound
11252
if self
113-
.0
11453
.image
11554
.iter()
116-
.zip(self.0.linear_combinations.iter())
55+
.zip(self.linear_combinations.iter())
11756
.any(|(&x, c)| x == G::identity() && !c.is_empty())
11857
{
11958
return Err(Error::InvalidInstanceWitnessPair);
12059
}
12160

122-
let nonces = (0..self.witness_length())
61+
let nonces = (0..self.num_scalars)
12362
.map(|_| G::Scalar::random(&mut *rng))
12463
.collect::<Vec<_>>();
125-
self.commit_with_nonces(witness, &nonces)
64+
65+
let commitment = self.evaluate(&nonces);
66+
let prover_state = (nonces.to_vec(), witness.to_vec());
67+
Ok((commitment, prover_state))
12668
}
12769

12870
/// Computes the prover's response (second message) using the challenge.
@@ -141,7 +83,7 @@ impl<G: PrimeGroup> SigmaProtocol for SchnorrProof<G> {
14183
prover_state: Self::ProverState,
14284
challenge: &Self::Challenge,
14385
) -> Result<Self::Response, Error> {
144-
let ProverState(nonces, witness) = prover_state;
86+
let (nonces, witness) = prover_state;
14587

14688
let responses = nonces
14789
.into_iter()
@@ -172,14 +114,14 @@ impl<G: PrimeGroup> SigmaProtocol for SchnorrProof<G> {
172114
challenge: &Self::Challenge,
173115
response: &Self::Response,
174116
) -> Result<(), Error> {
175-
if commitment.len() != self.commitment_length() || response.len() != self.witness_length() {
117+
if commitment.len() != self.image.len() || response.len() != self.num_scalars {
176118
return Err(Error::InvalidInstanceWitnessPair);
177119
}
178120

179-
let lhs = self.0.evaluate(response);
121+
let lhs = self.evaluate(response);
180122
let mut rhs = Vec::new();
181123
for (i, g) in commitment.iter().enumerate() {
182-
rhs.push(self.0.image[i] * challenge + g);
124+
rhs.push(self.image[i] * challenge + g);
183125
}
184126
if lhs == rhs {
185127
Ok(())
@@ -247,7 +189,7 @@ impl<G: PrimeGroup> SigmaProtocol for SchnorrProof<G> {
247189
/// # Errors
248190
/// - Returns [`Error::VerificationFailure`] if the data is malformed or contains an invalid encoding.
249191
fn deserialize_commitment(&self, data: &[u8]) -> Result<Self::Commitment, Error> {
250-
deserialize_elements::<G>(data, self.commitment_length()).ok_or(Error::VerificationFailure)
192+
deserialize_elements::<G>(data, self.image.len()).ok_or(Error::VerificationFailure)
251193
}
252194

253195
/// Deserializes a byte slice into a challenge scalar.
@@ -281,19 +223,19 @@ impl<G: PrimeGroup> SigmaProtocol for SchnorrProof<G> {
281223
/// # Errors
282224
/// - Returns [`Error::VerificationFailure`] if the byte data is malformed or the length is incorrect.
283225
fn deserialize_response(&self, data: &[u8]) -> Result<Self::Response, Error> {
284-
deserialize_scalars::<G>(data, self.witness_length()).ok_or(Error::VerificationFailure)
226+
deserialize_scalars::<G>(data, self.num_scalars).ok_or(Error::VerificationFailure)
285227
}
286228

287229
fn instance_label(&self) -> impl AsRef<[u8]> {
288-
self.0.label()
230+
self.label()
289231
}
290232

291233
fn protocol_identifier(&self) -> impl AsRef<[u8]> {
292234
b"draft-zkproof-fiat-shamir"
293235
}
294236
}
295237

296-
impl<G> SigmaProtocolSimulator for SchnorrProof<G>
238+
impl<G> SigmaProtocolSimulator for CanonicalLinearRelation<G>
297239
where
298240
G: PrimeGroup,
299241
{
@@ -306,7 +248,7 @@ where
306248
/// # Returns
307249
/// - A commitment and response forming a valid proof for the given challenge.
308250
fn simulate_response<R: Rng + CryptoRng>(&self, rng: &mut R) -> Self::Response {
309-
let response: Vec<G::Scalar> = (0..self.witness_length())
251+
let response: Vec<G::Scalar> = (0..self.num_scalars)
310252
.map(|_| G::Scalar::random(&mut *rng))
311253
.collect();
312254
response
@@ -345,16 +287,14 @@ where
345287
challenge: &Self::Challenge,
346288
response: &Self::Response,
347289
) -> Result<Self::Commitment, Error> {
348-
if response.len() != self.witness_length() {
290+
if response.len() != self.num_scalars {
349291
return Err(Error::InvalidInstanceWitnessPair);
350292
}
351293

352-
let response_image = self.0.evaluate(response);
353-
let image = &self.0.image;
354-
294+
let response_image = self.evaluate(response);
355295
let commitment = response_image
356296
.iter()
357-
.zip(image)
297+
.zip(&self.image)
358298
.map(|(res, img)| *res - *img * challenge)
359299
.collect::<Vec<_>>();
360300
Ok(commitment)

0 commit comments

Comments
 (0)