Skip to content

Commit 484b365

Browse files
committed
fix: rely on PrimeGroup trait instead of Group + GroupEncoding.
Group is not guaranteed to have a prime order, which may lead to small subgroup attacks.
1 parent 257ce61 commit 484b365

File tree

5 files changed

+127
-18
lines changed

5 files changed

+127
-18
lines changed

src/linear_relation/convert.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,3 +140,10 @@ impl<T, F: Field> From<Sum<T>> for Sum<Weighted<T, F>> {
140140
Self(sum.0.into_iter().map(|x| x.into()).collect())
141141
}
142142
}
143+
144+
// Manual implementation for ScalarTerm sum conversion
145+
impl<G: Group> From<ScalarTerm<G>> for Sum<Weighted<ScalarTerm<G>, G::Scalar>> {
146+
fn from(value: ScalarTerm<G>) -> Self {
147+
Sum(vec![value.into()])
148+
}
149+
}

src/linear_relation/ops.rs

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,14 @@ mod add {
252252
self
253253
}
254254
}
255+
256+
impl<G: Group> Add<Term<G>> for Weighted<GroupVar<G>, G::Scalar> {
257+
type Output = Sum<Weighted<Term<G>, G::Scalar>>;
258+
259+
fn add(self, rhs: Term<G>) -> Self::Output {
260+
rhs + self
261+
}
262+
}
255263
}
256264

257265
mod mul {
@@ -729,9 +737,9 @@ mod tests {
729737

730738
let diff = x - y;
731739
assert_eq!(diff.terms().len(), 2);
732-
assert_eq!(diff.terms()[0].term, y);
740+
assert_eq!(diff.terms()[0].term, y.into());
733741
assert_eq!(diff.terms()[0].weight, -Scalar::ONE);
734-
assert_eq!(diff.terms()[1].term, x);
742+
assert_eq!(diff.terms()[1].term, x.into());
735743
assert_eq!(diff.terms()[1].weight, Scalar::ONE);
736744
}
737745

@@ -1050,4 +1058,41 @@ mod tests {
10501058
assert_eq!(mixed.terms()[2].term.elem, k);
10511059
assert_eq!(mixed.terms()[2].weight, Scalar::from(3u64));
10521060
}
1061+
1062+
#[test]
1063+
fn test_scalar_var_minus_scalar_times_group() {
1064+
let x = scalar_var(0);
1065+
let b = group_var(0);
1066+
1067+
// Test the user's example: (x - Scalar::from_u128(1u128)) * B
1068+
// For now, demonstrate the equivalent: x * B + b * (-1)
1069+
let result = x * b + b * (-Scalar::ONE);
1070+
1071+
assert_eq!(result.terms().len(), 2);
1072+
assert_eq!(result.terms()[0].term.scalar, x.into());
1073+
assert_eq!(result.terms()[0].term.elem, b);
1074+
assert_eq!(result.terms()[0].weight, Scalar::ONE);
1075+
assert_eq!(result.terms()[1].term.scalar, ScalarTerm::Unit);
1076+
assert_eq!(result.terms()[1].term.elem, b);
1077+
assert_eq!(result.terms()[1].weight, -Scalar::ONE);
1078+
}
1079+
1080+
#[test]
1081+
fn test_group_var_times_scalar_plus_scalar_times_group() {
1082+
let gen__disj1_x_r = scalar_var(0);
1083+
let a = group_var(0);
1084+
let b = group_var(1);
1085+
1086+
// Test the user's example: A * Scalar::from_u128(1u128) + gen__disj1_x_r * B
1087+
let result = a * Scalar::ONE + gen__disj1_x_r * b;
1088+
1089+
assert_eq!(result.terms().len(), 2);
1090+
// The order is reversed from what we expected due to implementation details
1091+
assert_eq!(result.terms()[0].term.scalar, gen__disj1_x_r.into());
1092+
assert_eq!(result.terms()[0].term.elem, b);
1093+
assert_eq!(result.terms()[0].weight, Scalar::ONE);
1094+
assert_eq!(result.terms()[1].term.scalar, ScalarTerm::Unit);
1095+
assert_eq!(result.terms()[1].term.elem, a);
1096+
assert_eq!(result.terms()[1].weight, Scalar::ONE);
1097+
}
10531098
}

src/tests/relations.rs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use crate::fiat_shamir::Nizk;
66
use crate::tests::test_utils::{
77
bbs_blind_commitment_computation, discrete_logarithm, dleq, pedersen_commitment,
88
pedersen_commitment_dleq, translated_discrete_logarithm, translated_dleq,
9+
user_specific_linear_combination,
910
};
1011
use crate::{codec::ShakeCodec, schnorr_protocol::SchnorrProof};
1112

@@ -45,6 +46,12 @@ fn test_pedersen_commitment() {
4546
);
4647
}
4748

49+
#[test]
50+
fn test_user_specific_linear_combination() {
51+
let mut rng = OsRng;
52+
user_specific_linear_combination(G::random(&mut rng), Scalar::random(&mut rng));
53+
}
54+
4855
#[test]
4956
fn test_pedersen_commitment_dleq() {
5057
let mut rng = OsRng;
@@ -219,6 +226,34 @@ fn noninteractive_pedersen_commitment() {
219226
);
220227
}
221228

229+
#[test]
230+
fn noninteractive_user_specific_linear_combination() {
231+
let mut rng = OsRng;
232+
let (relation, witness) =
233+
user_specific_linear_combination(G::random(&mut rng), Scalar::random(&mut rng));
234+
235+
// The SigmaProtocol induced by relation
236+
let protocol = SchnorrProof::from(relation);
237+
// Fiat-Shamir wrapper
238+
let domain_sep = b"test-fiat-shamir-user-specific-linear-combination";
239+
let nizk = Nizk::<SchnorrProof<G>, ShakeCodec<G>>::new(domain_sep, protocol);
240+
241+
// Batchable and compact proofs
242+
let proof_batchable_bytes = nizk.prove_batchable(&witness, &mut rng).unwrap();
243+
let proof_compact_bytes = nizk.prove_compact(&witness, &mut rng).unwrap();
244+
// Verify proofs
245+
let verified_batchable = nizk.verify_batchable(&proof_batchable_bytes).is_ok();
246+
let verified_compact = nizk.verify_compact(&proof_compact_bytes).is_ok();
247+
assert!(
248+
verified_batchable,
249+
"Fiat-Shamir Schnorr proof verification failed"
250+
);
251+
assert!(
252+
verified_compact,
253+
"Fiat-Shamir Schnorr proof verification failed"
254+
);
255+
}
256+
222257
#[test]
223258
fn noninteractive_pedersen_commitment_dleq() {
224259
let mut rng = OsRng;

src/tests/spec/random.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
use group::{Group, GroupEncoding};
1+
use group::{prime::PrimeGroup, Group};
22
use num_bigint::BigUint;
33
use rand::{CryptoRng, Rng};
44

5-
pub trait SInput: Group + GroupEncoding {
5+
pub trait SInput: PrimeGroup {
66
fn scalar_from_hex_be(hex_str: &str) -> Option<Self::Scalar>;
77
}
88

src/tests/test_utils.rs

Lines changed: 36 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,13 @@
11
//! Definitions used in tests for this crate.
22
33
use ff::Field;
4-
use group::{Group, GroupEncoding};
4+
use group::{prime::PrimeGroup, Group};
55

66
use crate::linear_relation::{msm_pr, LinearRelation};
77

88
/// LinearMap for knowledge of a discrete logarithm relative to a fixed basepoint.
99
#[allow(non_snake_case)]
10-
pub fn discrete_logarithm<G: Group + GroupEncoding>(
11-
x: G::Scalar,
12-
) -> (LinearRelation<G>, Vec<G::Scalar>) {
10+
pub fn discrete_logarithm<G: PrimeGroup>(x: G::Scalar) -> (LinearRelation<G>, Vec<G::Scalar>) {
1311
let mut relation: LinearRelation<G> = LinearRelation::new();
1412

1513
let var_x = relation.allocate_scalar();
@@ -28,7 +26,7 @@ pub fn discrete_logarithm<G: Group + GroupEncoding>(
2826

2927
/// LinearMap for knowledge of a translated discrete logarithm relative to a fixed basepoint.
3028
#[allow(non_snake_case)]
31-
pub fn translated_discrete_logarithm<G: Group + GroupEncoding>(
29+
pub fn translated_discrete_logarithm<G: PrimeGroup>(
3230
x: G::Scalar,
3331
) -> (LinearRelation<G>, Vec<G::Scalar>) {
3432
let mut relation: LinearRelation<G> = LinearRelation::new();
@@ -49,7 +47,7 @@ pub fn translated_discrete_logarithm<G: Group + GroupEncoding>(
4947

5048
/// LinearMap for knowledge of a discrete logarithm equality between two pairs.
5149
#[allow(non_snake_case)]
52-
pub fn dleq<G: Group + GroupEncoding>(H: G, x: G::Scalar) -> (LinearRelation<G>, Vec<G::Scalar>) {
50+
pub fn dleq<G: PrimeGroup>(H: G, x: G::Scalar) -> (LinearRelation<G>, Vec<G::Scalar>) {
5351
let mut relation: LinearRelation<G> = LinearRelation::new();
5452

5553
let var_x = relation.allocate_scalar();
@@ -71,10 +69,7 @@ pub fn dleq<G: Group + GroupEncoding>(H: G, x: G::Scalar) -> (LinearRelation<G>,
7169

7270
/// LinearMap for knowledge of a translated dleq.
7371
#[allow(non_snake_case)]
74-
pub fn translated_dleq<G: Group + GroupEncoding>(
75-
H: G,
76-
x: G::Scalar,
77-
) -> (LinearRelation<G>, Vec<G::Scalar>) {
72+
pub fn translated_dleq<G: PrimeGroup>(H: G, x: G::Scalar) -> (LinearRelation<G>, Vec<G::Scalar>) {
7873
let mut relation: LinearRelation<G> = LinearRelation::new();
7974

8075
let var_x = relation.allocate_scalar();
@@ -96,7 +91,7 @@ pub fn translated_dleq<G: Group + GroupEncoding>(
9691

9792
/// LinearMap for knowledge of an opening to a Pedersen commitment.
9893
#[allow(non_snake_case)]
99-
pub fn pedersen_commitment<G: Group + GroupEncoding>(
94+
pub fn pedersen_commitment<G: PrimeGroup>(
10095
H: G,
10196
x: G::Scalar,
10297
r: G::Scalar,
@@ -120,7 +115,7 @@ pub fn pedersen_commitment<G: Group + GroupEncoding>(
120115

121116
/// LinearMap for knowledge of equal openings to two distinct Pedersen commitments.
122117
#[allow(non_snake_case)]
123-
pub fn pedersen_commitment_dleq<G: Group + GroupEncoding>(
118+
pub fn pedersen_commitment_dleq<G: PrimeGroup>(
124119
generators: [G; 4],
125120
witness: [G::Scalar; 2],
126121
) -> (LinearRelation<G>, Vec<G::Scalar>) {
@@ -150,7 +145,7 @@ pub fn pedersen_commitment_dleq<G: Group + GroupEncoding>(
150145
/// LinearMap for knowledge of an opening for use in a BBS commitment.
151146
// BBS message length is 3
152147
#[allow(non_snake_case)]
153-
pub fn bbs_blind_commitment_computation<G: Group + GroupEncoding>(
148+
pub fn bbs_blind_commitment_computation<G: PrimeGroup>(
154149
[Q_2, J_1, J_2, J_3]: [G; 4],
155150
[msg_1, msg_2, msg_3]: [G::Scalar; 3],
156151
secret_prover_blind: G::Scalar,
@@ -187,7 +182,7 @@ pub fn bbs_blind_commitment_computation<G: Group + GroupEncoding>(
187182

188183
/// Test function with the requested LinearRelation code
189184
#[allow(non_snake_case)]
190-
pub fn test_linear_relation_example<G: Group + GroupEncoding>() -> LinearRelation<G> {
185+
pub fn test_linear_relation_example<G: PrimeGroup>() -> LinearRelation<G> {
191186
use ff::Field;
192187

193188
let mut sigma__lr = LinearRelation::<G>::new();
@@ -199,3 +194,30 @@ pub fn test_linear_relation_example<G: Group + GroupEncoding>() -> LinearRelatio
199194
sigma__lr
200195
}
201196

197+
/// LinearMap for the user's specific relation: A * 1 + gen__disj1_x_r * B
198+
#[allow(non_snake_case)]
199+
pub fn user_specific_linear_combination<G: PrimeGroup>(
200+
B: G,
201+
gen__disj1_x_r: G::Scalar,
202+
) -> (LinearRelation<G>, Vec<G::Scalar>) {
203+
let mut sigma__lr = LinearRelation::<G>::new();
204+
205+
let gen__disj1_x_r_var = sigma__lr.allocate_scalar();
206+
let A = sigma__lr.allocate_element();
207+
let B_var = sigma__lr.allocate_element();
208+
209+
let sigma__eq1 =
210+
sigma__lr.allocate_eq(A * <G::Scalar as ff::Field>::ONE + gen__disj1_x_r_var * B_var);
211+
212+
// Set the group elements
213+
sigma__lr.set_elements([(A, G::generator()), (B_var, B)]);
214+
sigma__lr.compute_image(&[gen__disj1_x_r]).unwrap();
215+
216+
let result = sigma__lr.linear_map.group_elements.get(sigma__eq1).unwrap();
217+
218+
// Verify the relation computes correctly
219+
let expected = G::generator() + B * gen__disj1_x_r;
220+
assert_eq!(result, expected);
221+
222+
(sigma__lr, vec![gen__disj1_x_r])
223+
}

0 commit comments

Comments
 (0)