Skip to content

Commit a20606a

Browse files
committed
feat(proofs): add compact proof representation for sigma protocols
- feat: implement CompactProtocol trait for storing proofs as ('challenge', 'response') pairs - feat: add compact proof support to SchnorrProof, NISigmaProtocol, and ProofBuilder structures - test: update test files to verify compact proof functionality
1 parent d5570cc commit a20606a

File tree

7 files changed

+268
-69
lines changed

7 files changed

+268
-69
lines changed

src/fiat_shamir.rs

Lines changed: 76 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,7 @@
1414
//! - `G`: the group used for commitments and operations (`Group` trait).
1515
1616
use crate::{
17-
codec::Codec,
18-
SigmaProtocol,
19-
ProofError
17+
codec::Codec, CompactProtocol, ProofError, SigmaProtocol
2018
};
2119

2220
use group::{Group, GroupEncoding};
@@ -65,8 +63,12 @@ where
6563
}
6664
}
6765

68-
/// Produces a non-interactive proof for a witness and serializes it as a vector of bytes.
69-
pub fn prove(&mut self, witness: &P::Witness, rng: &mut (impl RngCore + CryptoRng)) -> Vec<u8> {
66+
/// Produces a non-interactive proof for a witness.
67+
pub fn prove(
68+
&mut self,
69+
witness: &P::Witness,
70+
rng: &mut (impl RngCore + CryptoRng)
71+
) -> (P::Commitment, P::Challenge, P::Response) {
7072
let mut codec = self.hash_state.clone();
7173

7274
let (commitment, prover_state) = self.sigmap.prover_commit(witness, rng);
@@ -86,16 +88,53 @@ where
8688
.sigmap
8789
.verifier(&commitment, &challenge, &response)
8890
.is_ok());
91+
(commitment, challenge, response)
92+
}
93+
94+
/// Verify a non-interactive proof and returns a Result: `Ok(())` if the proof verifies successfully, `Err(())` otherwise.
95+
pub fn verify(
96+
&mut self,
97+
commitment: &P::Commitment,
98+
challenge: &P::Challenge,
99+
response: &P::Response
100+
) -> Result<(), ProofError> {
101+
let mut codec = self.hash_state.clone();
102+
103+
// Commitment data for expected challenge generation
104+
let mut data = Vec::new();
105+
for commit in commitment {
106+
data.extend_from_slice(commit.to_bytes().as_ref());
107+
}
108+
// Recompute the challenge
109+
let expected_challenge = codec
110+
.prover_message(&data)
111+
.verifier_challenge();
112+
// Verification of the proof
113+
match *challenge == expected_challenge {
114+
true => self.sigmap.verifier(commitment, challenge, response),
115+
false => Err(ProofError::VerificationFailure),
116+
}
117+
}
118+
119+
pub fn prove_batchable(
120+
&mut self,
121+
witness: &P::Witness,
122+
rng: &mut (impl RngCore + CryptoRng)
123+
) -> Vec<u8> {
124+
let (commitment, challenge, response) = self.prove(witness, rng);
89125
self.sigmap
90126
.serialize_batchable(&commitment, &challenge, &response)
91127
}
92128

93-
/// Verify a non-interactive serialized proof and returns a Result: `Ok(())` if the proof verifies successfully, `Err(())` otherwise.
94-
pub fn verify(&mut self, proof: &[u8]) -> Result<(), ProofError> {
129+
pub fn verify_batchable(
130+
&mut self,
131+
proof: &[u8]
132+
) -> Result<(), ProofError> {
133+
let (commitment, response) = self.sigmap.deserialize_batchable(proof).unwrap();
134+
95135
let mut codec = self.hash_state.clone();
96136

97-
let (commitment, response) = self.sigmap.deserialize_batchable(proof).unwrap();
98-
// Commitment data for challenge generation
137+
// Commitment data for expected challenge generation
99138
let mut data = Vec::new();
100139
for commit in &commitment {
101140
data.extend_from_slice(commit.to_bytes().as_ref());
@@ -107,4 +146,32 @@ where
107146
// Verification of the proof
108147
self.sigmap.verifier(&commitment, &challenge, &response)
109148
}
149+
}
150+
151+
impl<P, C, G> NISigmaProtocol<P, C, G>
152+
where
153+
G: Group + GroupEncoding,
154+
P: SigmaProtocol<Commitment = Vec<G>, Challenge = <G as Group>::Scalar> + CompactProtocol,
155+
C: Codec<Challenge = <G as Group>::Scalar> + Clone,
156+
{
157+
pub fn prove_compact(
158+
&mut self,
159+
witness: &P::Witness,
160+
rng: &mut (impl RngCore + CryptoRng)
161+
) -> Vec<u8> {
162+
let (commitment, challenge, response) = self.prove(witness, rng);
163+
self.sigmap
164+
.serialize_compact(&commitment, &challenge, &response)
165+
}
166+
167+
pub fn verify_compact(
168+
&mut self,
169+
proof: &[u8]
170+
) -> Result<(), ProofError> {
171+
let (challenge, response) = self.sigmap.deserialize_compact(proof).unwrap();
172+
// Compute the commitments
173+
let commitment = self.sigmap.get_commitment(&challenge, &response);
174+
// Verify the proof
175+
self.verify(&commitment, &challenge, &response)
176+
}
110177
}

src/proof_builder.rs

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -101,10 +101,10 @@ where
101101
/// - `rng`: A random number generator
102102
///
103103
/// # Returns
104-
/// A serialized proof as a vector of bytes.
104+
/// A serialized proof as a vector of bytes in batchable ('commitment', 'response') format.
105105
pub fn prove(&mut self, witness: &[<G as Group>::Scalar], rng: &mut (impl RngCore + CryptoRng)) -> Vec<u8> {
106106
let witness_tmp = witness.to_vec();
107-
self.protocol.prove(&witness_tmp, rng)
107+
self.protocol.prove_batchable(&witness_tmp, rng)
108108
}
109109

110110
/// Verifies a serialized proof against the current statement.
@@ -115,6 +115,15 @@ where
115115
/// # Returns
116116
/// `Ok(())` if the proof is valid, or a [`ProofError`] if verification fails.
117117
pub fn verify(&mut self, proof: &[u8]) -> Result<(), ProofError> {
118-
self.protocol.verify(proof)
118+
self.protocol.verify_batchable(proof)
119+
}
120+
121+
pub fn prove_compact(&mut self, witness: &[<G as Group>::Scalar], rng: &mut (impl RngCore + CryptoRng)) -> Vec<u8> {
122+
let witness_tmp = witness.to_vec();
123+
self.protocol.prove_compact(&witness_tmp, rng)
124+
}
125+
126+
pub fn verify_compact(&mut self, proof: &[u8]) -> Result<(), ProofError> {
127+
self.protocol.verify_compact(proof)
119128
}
120129
}

src/schnorr_proof.rs

Lines changed: 94 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,7 @@
55
//! through a group morphism abstraction (see Maurer09).
66
77
use crate::{
8-
GroupMorphismPreimage,
9-
serialisation::GroupSerialisation,
10-
SigmaProtocol,
11-
ProofError,
8+
serialisation::GroupSerialisation, CompactProtocol, GroupMorphismPreimage, ProofError, SigmaProtocol
129
};
1310

1411
use ff::{Field, PrimeField};
@@ -81,59 +78,62 @@ where
8178
}
8279
}
8380

84-
/// Serializes the proof (`commitment`, `response`) into a batchable format for transmission.
81+
/// Serializes the proof into a batchable (`commitment`, `response`) format for transmission.
8582
fn serialize_batchable(
8683
&self,
8784
commitment: &Self::Commitment,
8885
_challenge: &Self::Challenge,
8986
response: &Self::Response,
9087
) -> Vec<u8> {
9188
let mut bytes = Vec::new();
92-
let scalar_nb = self.0.morphism.num_scalars;
93-
let point_nb = self.0.morphism.num_statements();
89+
let commit_nb = self.0.morphism.num_statements();
90+
let response_nb = self.0.morphism.num_scalars;
9491

9592
// Serialize commitments
96-
for commit in commitment.iter().take(point_nb) {
93+
for commit in commitment.iter().take(commit_nb) {
9794
bytes.extend_from_slice(&G::serialize_element(commit));
9895
}
9996

10097
// Serialize responses
101-
for response in response.iter().take(scalar_nb) {
98+
for response in response.iter().take(response_nb) {
10299
bytes.extend_from_slice(&G::serialize_scalar(response));
103100
}
104101
bytes
105102
}
106103

107104
/// Deserializes a batchable proof format back into (`commitment`, `response`).
108-
fn deserialize_batchable(&self, data: &[u8]) -> Option<(Self::Commitment, Self::Response)> {
109-
let scalar_nb = self.0.morphism.num_scalars;
110-
let point_nb = self.0.morphism.num_statements();
105+
fn deserialize_batchable(
106+
&self,
107+
data: &[u8]
108+
) -> Option<(Self::Commitment, Self::Response)> {
109+
let commit_nb = self.0.morphism.num_statements();
110+
let response_nb = self.0.morphism.num_scalars;
111111

112-
let point_size = G::generator().to_bytes().as_ref().len();
113-
let scalar_size = <<G as Group>::Scalar as PrimeField>::Repr::default()
112+
let commit_size = G::generator().to_bytes().as_ref().len();
113+
let response_size = <<G as Group>::Scalar as PrimeField>::Repr::default()
114114
.as_ref()
115115
.len();
116116

117-
let expected_len = scalar_nb * scalar_size + point_nb * point_size;
117+
let expected_len = response_nb * response_size + commit_nb * commit_size;
118118
if data.len() != expected_len {
119119
return None;
120120
}
121121

122122
let mut commitments: Self::Commitment = Vec::new();
123123
let mut responses: Self::Response = Vec::new();
124124

125-
for i in 0..point_nb {
126-
let start = i * point_size;
127-
let end = start + point_size;
125+
for i in 0..commit_nb {
126+
let start = i * commit_size;
127+
let end = start + commit_size;
128128

129129
let slice = &data[start..end];
130130
let elem = G::deserialize_element(slice)?;
131131
commitments.push(elem);
132132
}
133133

134-
for i in 0..scalar_nb {
135-
let start = point_nb * point_size + i * scalar_size;
136-
let end = start + scalar_size;
134+
for i in 0..response_nb {
135+
let start = commit_nb * commit_size + i * response_size;
136+
let end = start + response_size;
137137

138138
let slice = &data[start..end];
139139
let scalar = G::deserialize_scalar(slice)?;
@@ -143,3 +143,76 @@ where
143143
Some((commitments, responses))
144144
}
145145
}
146+
147+
impl<G> CompactProtocol for SchnorrProof<G>
148+
where
149+
G: Group + GroupEncoding + GroupSerialisation,
150+
{
151+
fn get_commitment(
152+
&self,
153+
challenge: &Self::Challenge,
154+
response: &Self::Response
155+
) -> Self::Commitment {
156+
let response_image = self.0.morphism.evaluate(response);
157+
let image= self.0.image();
158+
159+
let mut commitment = Vec::new();
160+
for i in 0..image.len() {
161+
commitment.push(response_image[i] - image[i] * challenge);
162+
}
163+
commitment
164+
}
165+
166+
/// Serializes the proof into a compact (`challenge`, `response`) format for transmission.
167+
fn serialize_compact(
168+
&self,
169+
_commitment: &Self::Commitment,
170+
challenge: &Self::Challenge,
171+
response: &Self::Response,
172+
) -> Vec<u8> {
173+
let mut bytes = Vec::new();
174+
let response_nb = self.0.morphism.num_scalars;
175+
176+
// Serialize challenge
177+
bytes.extend_from_slice(&G::serialize_scalar(challenge));
178+
179+
// Serialize responses
180+
for response in response.iter().take(response_nb) {
181+
bytes.extend_from_slice(&G::serialize_scalar(response));
182+
}
183+
bytes
184+
}
185+
186+
/// Deserializes a compact proof format back into (`challenge`, `response`).
187+
fn deserialize_compact(
188+
&self,
189+
data: &[u8]
190+
) -> Option<(Self::Challenge, Self::Response)> {
191+
let response_nb = self.0.morphism.num_scalars;
192+
let response_size = <<G as Group>::Scalar as PrimeField>::Repr::default()
193+
.as_ref()
194+
.len();
195+
196+
let expected_len = (response_nb + 1) * response_size;
197+
198+
if data.len() != expected_len {
199+
return None;
200+
}
201+
202+
let mut responses: Self::Response = Vec::new();
203+
204+
let slice = &data[0..response_size];
205+
let challenge = G::deserialize_scalar(slice)?;
206+
207+
for i in 0..response_nb {
208+
let start = (i + 1) * response_size;
209+
let end = start + response_size;
210+
211+
let slice = &data[start..end];
212+
let scalar = G::deserialize_scalar(slice)?;
213+
responses.push(scalar);
214+
}
215+
216+
Some((challenge, responses))
217+
}
218+
}

src/trait.rs

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,11 +79,38 @@ pub trait SigmaProtocol {
7979
///
8080
/// # Panics
8181
/// Panics if deserialization is not supported for this protocol.
82-
fn deserialize_batchable(&self, _data: &[u8]) -> Option<(Self::Commitment, Self::Response)> {
82+
fn deserialize_batchable(
83+
&self,
84+
_data: &[u8]
85+
) -> Option<(Self::Commitment, Self::Response)> {
8386
panic!("deserialize_batchable not implemented for this protocol")
8487
}
8588
}
8689

90+
pub trait CompactProtocol: SigmaProtocol {
91+
fn get_commitment(
92+
&self,
93+
challenge: &Self::Challenge,
94+
response: &Self::Response
95+
) -> Self::Commitment;
96+
97+
fn serialize_compact(
98+
&self,
99+
_commitment: &Self::Commitment,
100+
_challenge: &Self::Challenge,
101+
_response: &Self::Response,
102+
) -> Vec<u8> {
103+
panic!("serialize_compact not implemented for this protocol")
104+
}
105+
106+
fn deserialize_compact(
107+
&self,
108+
_data: &[u8]
109+
) -> Option<(Self::Challenge, Self::Response)> {
110+
panic!("deserialize_compact not implemented for this protocol")
111+
}
112+
}
113+
87114
/// A trait defining the behavior of a Sigma protocol for which simulation of transcripts is necessary.
88115
///
89116
/// All Sigma protocols can technically simulate a valid transcript, but this mostly serve to prove the security of the protocol and is not used in the real protocol execution.

0 commit comments

Comments
 (0)