Skip to content

Commit b55fd45

Browse files
committed
chore: add implementation and tests for more linear relation operations.
1 parent 83f20f7 commit b55fd45

File tree

5 files changed

+133
-56
lines changed

5 files changed

+133
-56
lines changed

src/linear_relation/convert.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -153,5 +153,4 @@ impl<G: Group> From<Sum<Weighted<GroupVar<G>, G::Scalar>>> for Sum<Weighted<Term
153153
let sum = sum.0.into_iter().map(|x| x.into()).collect::<Vec<_>>();
154154
Self(sum)
155155
}
156-
157-
}
156+
}

src/linear_relation/mod.rs

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -698,17 +698,13 @@ impl<G: PrimeGroup> TryFrom<&LinearRelation<G>> for CanonicalLinearRelation<G> {
698698
}
699699

700700
// If any linear combination has no witness variables, the relation is invalid
701-
if relation
702-
.linear_map
703-
.linear_combinations
704-
.iter()
705-
.any(|lc| lc.0.iter().all(|weighted| matches!(weighted.term.scalar, ScalarTerm::Unit)))
706-
{
701+
if relation.linear_map.linear_combinations.iter().any(|lc| {
702+
lc.0.iter()
703+
.all(|weighted| matches!(weighted.term.scalar, ScalarTerm::Unit))
704+
}) {
707705
return Err(Error::InvalidInstanceWitnessPair);
708706
}
709707

710-
711-
712708
let mut canonical = CanonicalLinearRelation::new();
713709
canonical.num_scalars = relation.linear_map.num_scalars;
714710

src/linear_relation/ops.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,30 @@ mod add {
268268
rhs + self
269269
}
270270
}
271+
272+
impl<T: Field + Into<G::Scalar>, G: Group> Add<T> for Sum<ScalarVar<G>> {
273+
type Output = Sum<Weighted<ScalarTerm<G>, G::Scalar>>;
274+
275+
fn add(self, rhs: T) -> Self::Output {
276+
// Convert Sum<ScalarVar<G>> to Sum<Weighted<ScalarTerm<G>, G::Scalar>>
277+
let mut weighted_terms = Vec::new();
278+
for var in self.0 {
279+
weighted_terms.push(Weighted {
280+
term: ScalarTerm::from(var),
281+
weight: G::Scalar::ONE,
282+
});
283+
}
284+
let weighted_sum: Sum<Weighted<ScalarTerm<G>, G::Scalar>> = Sum(weighted_terms);
285+
286+
// Convert the scalar to a weighted term
287+
let weighted_scalar = Weighted {
288+
term: ScalarTerm::Unit,
289+
weight: rhs.into(),
290+
};
291+
292+
weighted_sum + weighted_scalar
293+
}
294+
}
271295
}
272296

273297
mod mul {

src/tests/test_relations.rs

Lines changed: 88 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,12 @@
1-
use std::collections::HashMap;
2-
3-
use ff::Field;
1+
use ff::{Field, PrimeField};
42
use group::prime::PrimeGroup;
53
use rand::rngs::OsRng;
64
use rand::RngCore;
75

6+
use crate::codec::Shake128DuplexSponge;
87
use crate::fiat_shamir::Nizk;
9-
use crate::{
10-
codec::Shake128DuplexSponge, linear_relation::CanonicalLinearRelation,
11-
schnorr_protocol::SchnorrProof,
12-
};
13-
use ff::PrimeField;
8+
use crate::linear_relation::CanonicalLinearRelation;
9+
use crate::schnorr_protocol::SchnorrProof;
1410

1511
use crate::linear_relation::{msm_pr, LinearRelation};
1612

@@ -155,7 +151,10 @@ pub fn twisted_pedersen_commitment<G: PrimeGroup, R: RngCore>(
155151
let [var_x, var_r] = relation.allocate_scalars();
156152
let [var_G, var_H] = relation.allocate_elements();
157153

158-
relation.allocate_eq((var_x * G::Scalar::from(3)) * var_G + (var_r * G::Scalar::from(2) + G::Scalar::from(3)) * var_H);
154+
relation.allocate_eq(
155+
(var_x * G::Scalar::from(3)) * var_G
156+
+ (var_r * G::Scalar::from(2) + G::Scalar::from(3)) * var_H,
157+
);
159158

160159
relation.set_elements([(var_H, H), (var_G, G::generator())]);
161160
relation.compute_image(&[x, r]).unwrap();
@@ -202,7 +201,6 @@ pub fn pedersen_commitment_dleq<G: PrimeGroup, R: RngCore>(
202201
(instance, witness_vec)
203202
}
204203

205-
206204
/// LinearMap for knowledge of an opening for use in a BBS commitment.
207205
// BBS message length is 3
208206
#[allow(non_snake_case)]
@@ -270,8 +268,7 @@ pub fn weird_linear_combination<G: PrimeGroup, R: RngCore>(
270268
let A = sigma__lr.allocate_element();
271269
let var_B = sigma__lr.allocate_element();
272270

273-
let sigma__eq1 =
274-
sigma__lr.allocate_eq(A * G::Scalar::from(1) + gen__disj1_x_r_var * var_B);
271+
let sigma__eq1 = sigma__lr.allocate_eq(A * G::Scalar::from(1) + gen__disj1_x_r_var * var_B);
275272

276273
// Set the group elements
277274
sigma__lr.set_elements([(A, G::generator()), (var_B, B)]);
@@ -327,34 +324,92 @@ fn subtractions_with_shift<G: PrimeGroup, R: RngCore>(
327324
(instance, witness)
328325
}
329326

327+
/// LinearMap for the failing relation from cmz wallet test: W.balance = N.balance + I.price + fee
328+
#[allow(non_snake_case)]
329+
fn cmz_wallet_spend_relation<G: PrimeGroup, R: RngCore>(
330+
mut rng: &mut R,
331+
) -> (CanonicalLinearRelation<G>, Vec<G::Scalar>) {
332+
// Simulate the wallet spend relation from cmz
333+
let P_W = G::random(&mut rng);
334+
let P_I = G::random(&mut rng);
335+
let A = G::random(&mut rng);
336+
337+
// Secret values
338+
let n_balance = G::Scalar::random(&mut rng);
339+
let i_price = G::Scalar::random(&mut rng);
340+
let fee = G::Scalar::from(5u64);
341+
let z_w_balance = G::Scalar::random(&mut rng);
342+
343+
// W.balance = N.balance + I.price + fee
344+
let w_balance = n_balance + i_price + fee;
345+
346+
let mut relation = LinearRelation::new();
347+
348+
// Allocate variables
349+
let var_n_balance = relation.allocate_scalar();
350+
let var_i_price = relation.allocate_scalar();
351+
let var_z_w_balance = relation.allocate_scalar();
352+
353+
let var_P_W = relation.allocate_element();
354+
let var_P_I = relation.allocate_element();
355+
let var_A = relation.allocate_element();
356+
357+
// C_show_Hattr_W_balance = (N.balance + I.price + fee) * P_W + z_w_balance * A
358+
// This is the problematic expression that fails in cmz
359+
let var_C = relation.allocate_eq(
360+
(var_n_balance + var_i_price + G::Scalar::from(5u64)) * var_P_W + var_z_w_balance * var_A,
361+
);
362+
363+
relation.set_elements([(var_P_W, P_W), (var_P_I, P_I), (var_A, A)]);
364+
365+
relation
366+
.compute_image(&[n_balance, i_price, z_w_balance])
367+
.unwrap();
368+
369+
let C = relation.linear_map.group_elements.get(var_C).unwrap();
370+
let expected = P_W * w_balance + A * z_w_balance;
371+
assert_eq!(C, expected);
372+
373+
let witness = vec![n_balance, i_price, z_w_balance];
374+
let instance = (&relation).try_into().unwrap();
375+
(instance, witness)
376+
}
377+
330378
/// Generic helper function to test both relation correctness and NIZK functionality
331379
#[test]
332-
fn test_common_relations() {
380+
fn test_relations() {
333381
use group::Group;
334382
type G = bls12_381::G1Projective;
335383

336-
let mut instance_generators = HashMap::<
384+
let instance_generators: Vec<(
337385
&str,
338386
Box<dyn Fn(&mut OsRng) -> (CanonicalLinearRelation<G>, Vec<<G as Group>::Scalar>)>,
339-
>::new();
340-
341-
instance_generators.insert("dlog", Box::new(discrete_logarithm));
342-
instance_generators.insert("shifted_dlog", Box::new(shifted_dlog));
343-
instance_generators.insert("dleq", Box::new(dleq));
344-
instance_generators.insert("shifted_dleq", Box::new(shifted_dleq));
345-
instance_generators.insert("pedersen_commitment", Box::new(pedersen_commitment));
346-
instance_generators.insert("twisted_pedersen_commitment", Box::new(twisted_pedersen_commitment));
347-
instance_generators.insert(
348-
"pedersen_commitment_dleq",
349-
Box::new(pedersen_commitment_dleq),
350-
);
351-
instance_generators.insert("bbs_blind_commitment", Box::new(bbs_blind_commitment));
352-
instance_generators.insert(
353-
"weird_linear_combination",
354-
Box::new(weird_linear_combination),
355-
);
356-
instance_generators.insert("simple_subtractions", Box::new(simple_subtractions));
357-
instance_generators.insert("subtractions_with_shift", Box::new(subtractions_with_shift));
387+
)> = vec![
388+
("dlog", Box::new(discrete_logarithm)),
389+
("shifted_dlog", Box::new(shifted_dlog)),
390+
("dleq", Box::new(dleq)),
391+
("shifted_dleq", Box::new(shifted_dleq)),
392+
("pedersen_commitment", Box::new(pedersen_commitment)),
393+
(
394+
"twisted_pedersen_commitment",
395+
Box::new(twisted_pedersen_commitment),
396+
),
397+
(
398+
"pedersen_commitment_dleq",
399+
Box::new(pedersen_commitment_dleq),
400+
),
401+
("bbs_blind_commitment", Box::new(bbs_blind_commitment)),
402+
(
403+
"weird_linear_combination",
404+
Box::new(weird_linear_combination),
405+
),
406+
("simple_subtractions", Box::new(simple_subtractions)),
407+
("subtractions_with_shift", Box::new(subtractions_with_shift)),
408+
(
409+
"cmz_wallet_spend_relation",
410+
Box::new(cmz_wallet_spend_relation),
411+
),
412+
];
358413

359414
for (relation_name, relation_sampler) in instance_generators.iter() {
360415
let mut rng = OsRng;

src/tests/test_validation_criteria.rs

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,6 @@ mod instance_validation {
163163
let result = CanonicalLinearRelation::try_from(&linear_relation);
164164
assert!(result.is_err());
165165

166-
167166
// The following relation is for
168167
// X = B * x + B * pub_scalar + A * 3
169168
// and should be considered a valid instance.
@@ -172,7 +171,8 @@ mod instance_validation {
172171
let x_var = linear_relation.allocate_scalar();
173172
let B_var = linear_relation.allocate_element();
174173
let A_var = linear_relation.allocate_element();
175-
let X_var = linear_relation.allocate_eq(B_var * x_var + B_var * pub_scalar + A_var * Scalar::from(3));
174+
let X_var = linear_relation
175+
.allocate_eq(B_var * x_var + B_var * pub_scalar + A_var * Scalar::from(3));
176176

177177
linear_relation.set_element(B_var, B);
178178
linear_relation.set_element(A_var, A);
@@ -363,45 +363,45 @@ mod proof_validation {
363363
}
364364

365365
#[test]
366-
fn test_or_relation_wrong_branch() {
366+
fn test_or_relation() {
367367
// This test reproduces the issue from sigma_compiler's simple_or test
368368
// where an OR relation fails verification when using the wrong branch
369369
let mut rng = thread_rng();
370-
370+
371371
// Create generators
372372
// For this test, we'll use two different multiples of the generator
373373
let B = G::generator();
374374
let A = B * Scalar::from(42u64); // Different generator
375-
375+
376376
// Create scalars
377377
let x = Scalar::random(&mut rng);
378378
let y = Scalar::random(&mut rng);
379-
379+
380380
// Set C = y*B (so the second branch should be satisfied)
381381
let C = B * y;
382-
382+
383383
// Create the first branch: C = x*A
384384
let mut lr1 = LinearRelation::<G>::new();
385385
let x_var = lr1.allocate_scalar();
386386
let A_var = lr1.allocate_element();
387387
let eq1 = lr1.allocate_eq(x_var * A_var);
388388
lr1.set_element(A_var, A);
389389
lr1.set_element(eq1, C);
390-
390+
391391
// Create the second branch: C = y*B
392392
let mut lr2 = LinearRelation::<G>::new();
393393
let y_var = lr2.allocate_scalar();
394394
let B_var = lr2.allocate_element();
395395
let eq2 = lr2.allocate_eq(y_var * B_var);
396396
lr2.set_element(B_var, B);
397397
lr2.set_element(eq2, C);
398-
398+
399399
// Create OR composition
400400
let or_relation = ComposedRelation::Or(vec![
401401
ComposedRelation::from(lr1),
402402
ComposedRelation::from(lr2),
403403
]);
404-
404+
405405
// The issue from sigma_compiler: the witness is always using branch 0
406406
// even when branch 1 should be used
407407
let witness_wrong = ComposedWitness::Or(
@@ -411,17 +411,20 @@ mod proof_validation {
411411
ComposedWitness::Simple(vec![y]),
412412
],
413413
);
414-
414+
415415
// Test the bug: using branch 0 when C = y*B (branch 1 should be used)
416416
let nizk = Nizk::<_, KeccakByteSchnorrCodec<G>>::new(b"test_or_bug", or_relation);
417417
let proof_result = nizk.prove_batchable(&witness_wrong, &mut rng);
418-
418+
419419
// This currently passes but should fail - this is the bug!
420420
// The prover is using branch 0 (C = x*A) but C actually equals y*B
421421
match proof_result {
422422
Ok(proof) => {
423423
let verify_result = nizk.verify_batchable(&proof);
424-
println!("Bug reproduced: Proof with wrong branch verified: {:?}", verify_result.is_ok());
424+
println!(
425+
"Bug reproduced: Proof with wrong branch verified: {:?}",
426+
verify_result.is_ok()
427+
);
425428
assert!(
426429
verify_result.is_err(),
427430
"BUG: Proof should fail when using wrong branch in OR relation, but it passed!"

0 commit comments

Comments
 (0)