Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions src/schnorr_protocol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -147,11 +147,13 @@ where
}

let lhs = self.0.linear_map.evaluate(response)?;
let zero_vec = vec![<<G as Group>::Scalar as Field>::ZERO; self.0.linear_map.num_scalars];
let zero_image = self.0.linear_map.evaluate(&zero_vec)?;
let mut rhs = Vec::new();
for (i, g) in commitment.iter().enumerate() {
rhs.push({
let image_var = self.0.image[i];
self.0.linear_map.group_elements.get(image_var)? * challenge + g
(self.0.linear_map.group_elements.get(image_var)? - zero_image[i]) * challenge + g
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Taking the example of a relation of the form X = (x + 1) * G = x * G + G where the nonlinear part is G:

Before the correction, the verification consisted of checking the equality between:
lhs = (r + wc) * G + G
rhs = (w
G + G)c + (rG + G)
which is completely false since the relation contains a nonlinear part (the excess c*G component remains in rhs).

So, to correct this problem, we must subtract the image of the zero vector by the function before multiplying by c in rhs, which amounts to checking the equality between:
lhs = (r + wc) * G + G
rhs = ((w
G + G) - G)c + (rG + G) = (w*G)c + (rG + G)
which is now verified

});
}
if lhs == rhs {
Expand Down Expand Up @@ -322,13 +324,16 @@ where
return Err(Error::InvalidInstanceWitnessPair);
}

let zero_vec = vec![<<G as Group>::Scalar as Field>::ZERO; self.0.linear_map.num_scalars];
let zero_image = self.0.linear_map.evaluate(&zero_vec)?;
let response_image = self.0.linear_map.evaluate(response)?;
let image = self.0.image()?;

let commitment = response_image
.iter()
.zip(&image)
.map(|(res, img)| *res - *img * challenge)
.zip(&zero_image)
.map(|((res, img), z_img)| *res - (*img - *z_img) * challenge)
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Taking again the example of the relation X = (x + 1) * G:
with r the nonces, w the witness, T the commitment, and c the challenge,
we initially have:

response_image = (r + w·c)·G + G

image = w·G + G

To recover T, which by definition equals r·G + G, we used to compute:
response_image – image · c

This works in the case where we do not have a nonlinear component.
However, in this case it would give:
(r·G + G) – c·G = T – c·G

Therefore, in the fix, during the computation of T, we must subtract c · zero_image,
where zero_image is the image of the zero vector under the function.

.collect::<Vec<_>>();
Ok(commitment)
}
Expand Down
35 changes: 34 additions & 1 deletion src/tests/relations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use rand::rngs::OsRng;
use crate::fiat_shamir::NISigmaProtocol;
use crate::tests::test_utils::{
bbs_blind_commitment_computation, discrete_logarithm, dleq, pedersen_commitment,
pedersen_commitment_dleq,
pedersen_commitment_dleq, translated_discrete_logarithm,
};
use crate::{codec::ShakeCodec, schnorr_protocol::SchnorrProof};

Expand All @@ -17,6 +17,12 @@ fn test_discrete_logarithm() {
discrete_logarithm::<G>(Scalar::random(&mut rng));
}

#[test]
fn test_translated_discrete_logarithm() {
let mut rng = OsRng;
translated_discrete_logarithm::<G>(Scalar::random(&mut rng));
}

#[test]
fn test_dleq() {
let mut rng = OsRng;
Expand Down Expand Up @@ -97,6 +103,33 @@ fn noninteractive_discrete_logarithm() {
);
}

#[test]
fn noninteractive_translated_discrete_logarithm() {
let mut rng = OsRng;
let (relation, witness) = translated_discrete_logarithm(Scalar::random(&mut rng));

// The SigmaProtocol induced by relation
let protocol = SchnorrProof::from(relation);
// Fiat-Shamir wrapper
let domain_sep = b"test-fiat-shamir-schnorr";
let nizk = NISigmaProtocol::<SchnorrProof<G>, ShakeCodec<G>>::new(domain_sep, protocol);

// Batchable and compact proofs
let proof_batchable_bytes = nizk.prove_batchable(&witness, &mut rng).unwrap();
let proof_compact_bytes = nizk.prove_compact(&witness, &mut rng).unwrap();
// Verify proofs
let verified_batchable = nizk.verify_batchable(&proof_batchable_bytes).is_ok();
let verified_compact = nizk.verify_compact(&proof_compact_bytes).is_ok();
assert!(
verified_batchable,
"Fiat-Shamir Schnorr proof verification failed"
);
assert!(
verified_compact,
"Fiat-Shamir Schnorr proof verification failed"
);
}

#[test]
fn noninteractive_dleq() {
let mut rng = OsRng;
Expand Down
45 changes: 30 additions & 15 deletions src/tests/test_utils.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
//! Definitions used in tests for this crate.

use ff::Field;
use group::{Group, GroupEncoding};

use crate::linear_relation::{msm_pr, LinearRelation};
Expand All @@ -25,6 +26,27 @@ pub fn discrete_logarithm<G: Group + GroupEncoding>(
(relation, vec![x])
}

/// LinearMap for knowledge of a translated discrete logarithm relative to a fixed basepoint.
#[allow(non_snake_case)]
pub fn translated_discrete_logarithm<G: Group + GroupEncoding>(
Copy link
Collaborator Author

@nougzarm nougzarm Jul 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Implementation of the relation X = (x + 1) * G, viewed as a “translated” discrete logarithm, in order to perform tests on the protocol induced by it.

Here, the function corresponds to x -> (x + 1)*G, and the part called "nonlinear" is the image of zero, so G

x: G::Scalar,
) -> (LinearRelation<G>, Vec<G::Scalar>) {
let mut relation: LinearRelation<G> = LinearRelation::new();

let var_x = relation.allocate_scalar();
let var_G = relation.allocate_element();

let var_X = relation.allocate_eq((var_x + <<G as Group>::Scalar as Field>::ONE) * var_G);

relation.set_element(var_G, G::generator());
relation.compute_image(&[x]).unwrap();

let X = relation.linear_map.group_elements.get(var_X).unwrap();

assert!(vec![X] == relation.linear_map.evaluate(&[x]).unwrap());
(relation, vec![x])
}

/// LinearMap for knowledge of a discrete logarithm equality between two pairs.
#[allow(non_snake_case)]
pub fn dleq<G: Group + GroupEncoding>(H: G, x: G::Scalar) -> (LinearRelation<G>, Vec<G::Scalar>) {
Expand Down Expand Up @@ -85,7 +107,8 @@ pub fn pedersen_commitment_dleq<G: Group + GroupEncoding>(
let [var_x, var_r] = relation.allocate_scalars();

let var_Gs = relation.allocate_elements::<4>();
let [var_X, var_Y] = relation.allocate_elements();
let var_X = relation.allocate_eq(var_x * var_Gs[0] + var_r * var_Gs[1]);
let var_Y = relation.allocate_eq(var_x * var_Gs[2] + var_r * var_Gs[3]);

relation.set_elements([
(var_Gs[0], generators[0]),
Expand All @@ -95,9 +118,6 @@ pub fn pedersen_commitment_dleq<G: Group + GroupEncoding>(
]);
relation.set_elements([(var_X, X), (var_Y, Y)]);

relation.append_equation(var_X, [(var_x, var_Gs[0]), (var_r, var_Gs[1])]);
relation.append_equation(var_Y, [(var_x, var_Gs[2]), (var_r, var_Gs[3])]);

assert!(vec![X, Y] == relation.linear_map.evaluate(&witness).unwrap());
(relation, witness.to_vec())
}
Expand All @@ -119,7 +139,12 @@ pub fn bbs_blind_commitment_computation<G: Group + GroupEncoding>(
let [var_secret_prover_blind, var_msg_1, var_msg_2, var_msg_3] = relation.allocate_scalars();

let [var_Q_2, var_J_1, var_J_2, var_J_3] = relation.allocate_elements();
let var_C = relation.allocate_element();
let var_C = relation.allocate_eq(
var_secret_prover_blind * var_Q_2
+ var_msg_1 * var_J_1
+ var_msg_2 * var_J_2
+ var_msg_3 * var_J_3,
);

relation.set_elements([
(var_Q_2, Q_2),
Expand All @@ -129,16 +154,6 @@ pub fn bbs_blind_commitment_computation<G: Group + GroupEncoding>(
(var_C, C),
]);

relation.append_equation(
var_C,
[
(var_secret_prover_blind, var_Q_2),
(var_msg_1, var_J_1),
(var_msg_2, var_J_2),
(var_msg_3, var_J_3),
],
);

let witness = vec![secret_prover_blind, msg_1, msg_2, msg_3];

assert!(vec![C] == relation.linear_map.evaluate(&witness).unwrap());
Expand Down
Loading