Skip to content

Commit 77ac92f

Browse files
committed
test(verification): add Sage test vectors and enhance documentation
- test: implement hard-coded Sage test vectors for automatic verification - docs: add documentation for CompactProtocol, ProofBuilder, and error handling
1 parent e1de7e8 commit 77ac92f

File tree

10 files changed

+60
-43
lines changed

10 files changed

+60
-43
lines changed

src/errors.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
//! These errors include:
77
//! - Verification failures (e.g., when a proof does not verify correctly).
88
//! - Mismatched parameters during batch verification.
9+
//! - Not implemented methods
10+
//! - Group element/scalar serialization failed
911
use thiserror::Error;
1012
/// An error during proving or verification, such as a verification failure.
1113
#[derive(Debug, Error)]
@@ -21,5 +23,5 @@ pub enum ProofError {
2123
NotImplemented(&'static str),
2224
/// Serialization of a group element/scalar failed
2325
#[error("Serialization of a group element/scalar failed")]
24-
GroupSerialisationFailure,
26+
GroupSerializationFailure,
2527
}
File renamed without changes.

src/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
pub mod errors;
1717
pub mod fiat_shamir;
1818
pub mod group_morphism;
19-
pub mod group_serialisation;
19+
pub mod group_serialization;
2020
pub mod proof_builder;
2121
pub mod proof_composition;
2222
pub mod schnorr_protocol;
@@ -25,7 +25,7 @@ pub mod r#trait;
2525
pub use errors::*;
2626
pub use fiat_shamir::*;
2727
pub use group_morphism::*;
28-
pub use group_serialisation::*;
28+
pub use group_serialization::*;
2929
pub use proof_builder::*;
3030
pub use proof_composition::*;
3131
pub use r#trait::*;

src/proof_builder.rs

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,17 +106,25 @@ where
106106
self.protocol.prove_batchable(&witness_tmp, rng)
107107
}
108108

109-
/// Verifies a serialized proof against the current statement.
109+
/// Verifies a serialized batchable proof against the current statement.
110110
///
111111
/// # Parameters
112-
/// - `proof`: A byte slice containing the serialized proof
112+
/// - `proof`: A byte slice containing the serialized batchable proof
113113
///
114114
/// # Returns
115115
/// `Ok(())` if the proof is valid, or a [`ProofError`] if verification fails.
116116
pub fn verify(&mut self, proof: &[u8]) -> Result<(), ProofError> {
117117
self.protocol.verify_batchable(proof)
118118
}
119119

120+
/// Generates a compact proof for the current statement using the given witness.
121+
///
122+
/// # Parameters
123+
/// - `witness`: A list of scalars (one per allocated scalar variable)
124+
/// - `rng`: A random number generator
125+
///
126+
/// # Returns
127+
/// A serialized proof as a vector of bytes in compact ('challenge', 'response') format.
120128
pub fn prove_compact(
121129
&mut self,
122130
witness: &[<G as Group>::Scalar],
@@ -126,6 +134,13 @@ where
126134
self.protocol.prove_compact(&witness_tmp, rng)
127135
}
128136

137+
/// Verifies a serialized compact proof against the current statement.
138+
///
139+
/// # Parameters
140+
/// - `proof`: A byte slice containing the serialized compact proof
141+
///
142+
/// # Returns
143+
/// `Ok(())` if the proof is valid, or a [`ProofError`] if verification fails.
129144
pub fn verify_compact(&mut self, proof: &[u8]) -> Result<(), ProofError> {
130145
self.protocol.verify_compact(proof)
131146
}

src/proof_composition.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -120,15 +120,15 @@ where
120120

121121
fn deserialize_batchable(&self, data: &[u8]) -> Result<(Self::Commitment, Self::Response), ProofError> {
122122
if data.len() < 4 {
123-
return Err(ProofError::GroupSerialisationFailure); // not enough bytes to contain the length suffix
123+
return Err(ProofError::GroupSerializationFailure); // not enough bytes to contain the length suffix
124124
}
125125

126126
// Split off the last 4 bytes as the trailer
127127
let (proof_data, len_bytes) = data.split_at(data.len() - 4);
128128
let len0 = u32::from_le_bytes(len_bytes.try_into().unwrap()) as usize;
129129

130130
if proof_data.len() < len0 {
131-
return Err(ProofError::GroupSerialisationFailure); // length hint exceeds available bytes
131+
return Err(ProofError::GroupSerializationFailure); // length hint exceeds available bytes
132132
}
133133

134134
let (ser0, ser1) = proof_data.split_at(len0);
@@ -309,7 +309,7 @@ where
309309
// The challenge is appended as `Challenge::Repr`, which must be a fixed size
310310
let repr_len = <C as PrimeField>::Repr::default().as_ref().len();
311311
if data.len() < repr_len + 4 {
312-
return Err(ProofError::GroupSerialisationFailure);
312+
return Err(ProofError::GroupSerializationFailure);
313313
}
314314

315315
let len0_bytes = &data[data.len() - 4..];
@@ -318,15 +318,15 @@ where
318318

319319
let len0 = u32::from_le_bytes(len0_bytes.try_into().unwrap()) as usize;
320320
if proof_data.len() < len0 {
321-
return Err(ProofError::GroupSerialisationFailure);
321+
return Err(ProofError::GroupSerializationFailure);
322322
}
323323

324324
let mut repr = <C as PrimeField>::Repr::default();
325325
repr.as_mut().copy_from_slice(ch0_bytes);
326326

327327
let result_ctoption = C::from_repr(repr);
328328
if (!result_ctoption.is_some()).into() {
329-
return Err(ProofError::GroupSerialisationFailure);
329+
return Err(ProofError::GroupSerializationFailure);
330330
}
331331
let ch0 = result_ctoption.unwrap();
332332

src/schnorr_protocol.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
//! through a group morphism abstraction (see Maurer09).
66
77
use crate::{
8-
group_serialisation::*,
8+
group_serialization::*,
99
CompactProtocol, GroupMorphismPreimage, ProofError,
1010
SigmaProtocol,
1111
};
@@ -131,7 +131,7 @@ where
131131
let end = start + commit_size;
132132

133133
let slice = &data[start..end];
134-
let elem = deserialize_element(slice).ok_or(ProofError::GroupSerialisationFailure)?;
134+
let elem = deserialize_element(slice).ok_or(ProofError::GroupSerializationFailure)?;
135135
commitments.push(elem);
136136
}
137137

@@ -140,7 +140,7 @@ where
140140
let end = start + response_size;
141141

142142
let slice = &data[start..end];
143-
let scalar = deserialize_scalar::<G>(slice).ok_or(ProofError::GroupSerialisationFailure)?;
143+
let scalar = deserialize_scalar::<G>(slice).ok_or(ProofError::GroupSerializationFailure)?;
144144
responses.push(scalar);
145145
}
146146

@@ -206,14 +206,14 @@ where
206206
let mut responses: Self::Response = Vec::new();
207207

208208
let slice = &data[0..response_size];
209-
let challenge = deserialize_scalar::<G>(slice).ok_or(ProofError::GroupSerialisationFailure)?;
209+
let challenge = deserialize_scalar::<G>(slice).ok_or(ProofError::GroupSerializationFailure)?;
210210

211211
for i in 0..response_nb {
212212
let start = (i + 1) * response_size;
213213
let end = start + response_size;
214214

215215
let slice = &data[start..end];
216-
let scalar = deserialize_scalar::<G>(slice).ok_or(ProofError::GroupSerialisationFailure)?;
216+
let scalar = deserialize_scalar::<G>(slice).ok_or(ProofError::GroupSerializationFailure)?;
217217
responses.push(scalar);
218218
}
219219

src/trait.rs

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -60,10 +60,7 @@ pub trait SigmaProtocol {
6060
response: &Self::Response,
6161
) -> Result<(), ProofError>;
6262

63-
/// Serializes a proof transcript (commitment, challenge, response) to bytes for batching.
64-
///
65-
/// # Panics
66-
/// Panics if serialization is not supported for this protocol.
63+
/// Serializes a proof transcript (commitment, challenge, response) to bytes batchable proof.
6764
fn serialize_batchable(
6865
&self,
6966
_commitment: &Self::Commitment,
@@ -73,12 +70,9 @@ pub trait SigmaProtocol {
7370
Err(ProofError::NotImplemented("serialize_batchable not implemented for this protocol"))
7471
}
7572

76-
/// Deserializes a proof transcript from bytes.
73+
/// Deserializes a batchable proof from bytes.
7774
///
7875
/// Returns `Some((commitment, response))` if parsing is successful, otherwise `None`.
79-
///
80-
/// # Panics
81-
/// Panics if deserialization is not supported for this protocol.
8276
fn deserialize_batchable(
8377
&self,
8478
_data: &[u8]
@@ -87,13 +81,25 @@ pub trait SigmaProtocol {
8781
}
8882
}
8983

84+
/// A feature defining the behavior of a protocol for which it is possible to compact the proofs by omitting the commitments.
85+
///
86+
/// This is possible if it is possible to retrieve the commitments from the challenge and responses.
87+
/// This is what the get_commitment function is for.
88+
///
89+
/// ## Minimal Implementation
90+
/// Types implementing `CompactProtocol` must define:
91+
/// - `get_commitment`
9092
pub trait CompactProtocol: SigmaProtocol {
93+
/// Returns the commitment for which ('commitment', 'challenge', 'response') is a valid transcription
94+
///
95+
/// This function allows to omit commitment in compact proofs of the type ('challenge', 'response')
9196
fn get_commitment(
9297
&self,
9398
challenge: &Self::Challenge,
9499
response: &Self::Response,
95100
) -> Self::Commitment;
96101

102+
/// Serializes a proof transcript (commitment, challenge, response) to bytes compact proof.
97103
fn serialize_compact(
98104
&self,
99105
_commitment: &Self::Commitment,
@@ -103,6 +109,9 @@ pub trait CompactProtocol: SigmaProtocol {
103109
Err(ProofError::NotImplemented("serialize_compact not implemented for this protocol"))
104110
}
105111

112+
/// Deserializes a compact proof from bytes.
113+
///
114+
/// Returns `Some((challenge, response))` if parsing is successful, otherwise `None`.
106115
fn deserialize_compact(
107116
&self,
108117
_data: &[u8]
@@ -124,19 +133,13 @@ pub trait SigmaProtocolSimulator: SigmaProtocol {
124133
/// Simulates a protocol transcript given a challenge.
125134
///
126135
/// This serves to create zero-knowledge simulations without access to a witness.
127-
///
128-
/// # Panics
129-
/// Panics if simulation is not implemented for this protocol.
130136
fn simulate_proof(
131137
&self,
132138
challenge: &Self::Challenge,
133139
rng: &mut (impl Rng + CryptoRng),
134140
) -> (Self::Commitment, Self::Response);
135141

136142
/// Simulates an entire protocol transcript.
137-
///
138-
/// # Panics
139-
/// Panics if simulation is not implemented for this protocol.
140143
fn simulate_transcript(
141144
&self,
142145
rng: &mut (impl Rng + CryptoRng),

tests/spec/custom_schnorr_protocol.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use group::{Group, GroupEncoding};
33
use rand::{CryptoRng, Rng};
44

55
use sigma_rs::{
6-
group_serialisation::*,
6+
group_serialization::*,
77
GroupMorphismPreimage, ProofError, SigmaProtocol,
88
};
99

@@ -121,7 +121,7 @@ where
121121
let end = start + point_size;
122122

123123
let slice = &data[start..end];
124-
let elem = deserialize_element(slice).ok_or(ProofError::GroupSerialisationFailure)?;
124+
let elem = deserialize_element(slice).ok_or(ProofError::GroupSerializationFailure)?;
125125
commitments.push(elem);
126126
}
127127

@@ -130,7 +130,7 @@ where
130130
let end = start + scalar_size;
131131

132132
let slice = data[start..end].to_vec();
133-
let scalar = deserialize_scalar::<G>(&slice).ok_or(ProofError::GroupSerialisationFailure)?;
133+
let scalar = deserialize_scalar::<G>(&slice).ok_or(ProofError::GroupSerializationFailure)?;
134134
responses.push(scalar);
135135
}
136136

tests/spec/sage_test_vectors.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,7 @@ fn NI_discrete_logarithm(seed: &[u8], context: &[u8]) -> (Vec<Scalar>, Vec<u8>)
209209
let proof_bytes = nizk.prove_batchable(&witness, &mut rng);
210210
let verified = nizk.verify_batchable(&proof_bytes).is_ok();
211211
assert!(verified, "Fiat-Shamir Schnorr proof verification failed");
212+
assert!(encode(&proof_bytes).as_bytes() == b"80c96c2822d816de609d4b72dd0b2a9409a3402338c977467225e7f506a60f3153a7f447450d7336c0ef15e4151349d91495306d216d5fe2ff3e660bcaf227c4794cb0e0887f5bcff6d4a6189cf9a494");
212213
(witness, proof_bytes)
213214
}
214215

@@ -224,6 +225,7 @@ fn NI_dleq(seed: &[u8], context: &[u8]) -> (Vec<Scalar>, Vec<u8>) {
224225
let proof_bytes = nizk.prove_batchable(&witness, &mut rng);
225226
let verified = nizk.verify_batchable(&proof_bytes).is_ok();
226227
assert!(verified, "Fiat-Shamir Schnorr proof verification failed");
228+
assert!(encode(&proof_bytes).as_bytes() == b"a01abd54895b7df2d476b2371e1796278a114f7dd1514e05cc1c0c07d40957268684c8887aa3f8cee33856ca325412f5a4fffa7226a983c8fcd9bb59dbb7a72e5c4eacd80958c3685d7abaa477ba6d738b35998ea1d0089166d17ea0a206d2991bf0b87f1f5c977f93fdccf9ec820d989656662f146460d48e56bfc2f6482285");
227229
(witness, proof_bytes)
228230
}
229231

@@ -239,6 +241,7 @@ fn NI_pedersen_commitment(seed: &[u8], context: &[u8]) -> (Vec<Scalar>, Vec<u8>)
239241
let proof_bytes = nizk.prove_batchable(&witness, &mut rng);
240242
let verified = nizk.verify_batchable(&proof_bytes).is_ok();
241243
assert!(verified, "Fiat-Shamir Schnorr proof verification failed");
244+
assert!(encode(&proof_bytes).as_bytes() == b"91c620e60e68502ab1e0f0fa6b9f7e3225f678596da80c0e950e4149078562518ad37ed6177c71ebd6e2ca5fc32457d8228aa82bf0293a2d70def71e0e1f434af472458907c4827b694987a903126dd050b3ed6234dcd4d176f05582d3dab5515f790c5cdc927972d631a2ddceb53edb");
242245
(witness, proof_bytes)
243246
}
244247

@@ -254,6 +257,7 @@ fn NI_pedersen_commitment_dleq(seed: &[u8], context: &[u8]) -> (Vec<Scalar>, Vec
254257
let proof_bytes = nizk.prove_batchable(&witness, &mut rng);
255258
let verified = nizk.verify_batchable(&proof_bytes).is_ok();
256259
assert!(verified, "Fiat-Shamir Schnorr proof verification failed");
260+
assert!(encode(&proof_bytes).as_bytes() == b"8e670749a002c02e0b343a47c0194743d9164d5026ddec0a9572a742748305f83b2fc679858f2f97debd72a08ec59dc38e5d6c8cc6cb284f4012d4eb41a807d1463ad0d8976f78baff1da1fdf2ad39027e8c66e0625b15740a72fc9e866f1d1014a32947fd44c55553eb2c13d21d639640b5d070987d8befea62367b235278d80a313d50f72e5c70de5fc1db95e042b3723344136144cc71c5515c5aa03d95d1");
257261
(witness, proof_bytes)
258262
}
259263

@@ -269,6 +273,7 @@ fn NI_bbs_blind_commitment_computation(seed: &[u8], context: &[u8]) -> (Vec<Scal
269273
let proof_bytes = nizk.prove_batchable(&witness, &mut rng);
270274
let verified = nizk.verify_batchable(&proof_bytes).is_ok();
271275
assert!(verified, "Fiat-Shamir Schnorr proof verification failed");
276+
assert!(encode(&proof_bytes).as_bytes() == b"803d5d4fdb311967832758ae7402d03304b570f97c0756e5385a50622d0ac7b5de87fe14d15041b1564ba4893a1187304ed12592b9ca9c5ca92a87c3960f0bcae541ddf880271c361cca15c67e13bc504cf96235363e99bb3e126b111c220c77427873389d2397cf0798d251ec82ced1649b5d0e9b2f95410a68b5b66158e50832488e540853a8c79a17d8b8290266ec150af102dd9ca4a6f076399da893b1f2caa78d192590708c02ab561eb3a01aa1");
272277
(witness, proof_bytes)
273278
}
274279

tests/spec/serialisation.rs renamed to tests/spec/serialization.rs

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,3 @@
1-
//! Serialization for Group Elements and Scalars
2-
//!
3-
//! This module defines the [`GroupSerialisation`] trait, which provides a unified interface
4-
//! for serializing and deserializing group elements and their associated scalar field elements.
5-
//!
6-
//! It is used throughout the Sigma protocol framework for encoding proof data, hashing to transcripts,
7-
//! and verifying correctness of received data.
8-
91
use group::{Group, GroupEncoding};
102
use ff::PrimeField;
113
use std::convert::TryInto;
@@ -34,7 +26,7 @@ use std::convert::TryInto;
3426
/// - `deserialize_element`: Attempts to decode a group element from bytes
3527
/// - `serialize_scalar`: Encodes a scalar field element into bytes
3628
/// - `deserialize_scalar`: Attempts to decode a scalar from bytes
37-
pub trait GroupSerialisation: Group + GroupEncoding {
29+
pub trait GroupSerialization: Group + GroupEncoding {
3830
/// Serializes a group element (e.g., elliptic curve point) into a canonical byte representation.
3931
///
4032
/// This representation must be deterministic and compatible with the corresponding
@@ -66,7 +58,7 @@ use curve25519_dalek::{
6658
};
6759

6860

69-
impl GroupSerialisation for RistrettoPoint {
61+
impl GroupSerialization for RistrettoPoint {
7062
/// Serializes a `RistrettoPoint` to 32-byte compressed form.
7163
fn serialize_element(point: &Self) -> Vec<u8> {
7264
point.compress().to_bytes().to_vec()
@@ -101,7 +93,7 @@ impl GroupSerialisation for RistrettoPoint {
10193
}
10294
}
10395

104-
impl GroupSerialisation for G1Projective {
96+
impl GroupSerialization for G1Projective {
10597
/// Serializes a `G1Projective` element using compressed affine representation (48 bytes).
10698
fn serialize_element(point: &G1Projective) -> Vec<u8> {
10799
let affine = G1Affine::from(point);

0 commit comments

Comments
 (0)