Skip to content

Commit 2a29ac1

Browse files
authored
chore(test): generalize the range proof test relation (#80)
1 parent 0956fc0 commit 2a29ac1

File tree

2 files changed

+103
-37
lines changed

2 files changed

+103
-37
lines changed

src/linear_relation/mod.rs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -407,6 +407,23 @@ impl<G: PrimeGroup> LinearRelation<G> {
407407
vars
408408
}
409409

410+
/// Allocates a vector of new scalar variables.
411+
///
412+
/// # Returns
413+
/// A vector of [`ScalarVar`] representing the newly allocated scalar indices.
414+
/// /// # Example
415+
/// ```
416+
/// # use sigma_proofs::LinearRelation;
417+
/// use curve25519_dalek::RistrettoPoint as G;
418+
///
419+
/// let mut relation = LinearRelation::<G>::new();
420+
/// let [var_x, var_y] = relation.allocate_scalars();
421+
/// let vars = relation.allocate_scalars_vec(10);
422+
/// ```
423+
pub fn allocate_scalars_vec(&mut self, n: usize) -> Vec<ScalarVar<G>> {
424+
(0..n).map(|_| self.allocate_scalar()).collect()
425+
}
426+
410427
/// Allocates a point variable (group element) for use in the linear map.
411428
pub fn allocate_element(&mut self) -> GroupVar<G> {
412429
self.linear_map.num_elements += 1;
@@ -442,6 +459,24 @@ impl<G: PrimeGroup> LinearRelation<G> {
442459
vars
443460
}
444461

462+
/// Allocates a vector of new point variables (group elements).
463+
///
464+
/// # Returns
465+
/// A vector of [`GroupVar`] representing the newly allocated group element indices.
466+
///
467+
/// # Example
468+
/// ```
469+
/// # use sigma_proofs::LinearRelation;
470+
/// use curve25519_dalek::RistrettoPoint as G;
471+
/// let mut relation = LinearRelation::<G>::new();
472+
/// let [var_g, var_h
473+
/// ] = relation.allocate_elements();
474+
/// let vars = relation.allocate_elements_vec(10);
475+
/// ```
476+
pub fn allocate_elements_vec(&mut self, n: usize) -> Vec<GroupVar<G>> {
477+
(0..n).map(|_| self.allocate_element()).collect()
478+
}
479+
445480
/// Allocates a point variable (group element) and sets it immediately to the given value.
446481
pub fn allocate_elements_with(&mut self, elements: &[G]) -> Vec<GroupVar<G>> {
447482
elements

src/tests/test_relations.rs

Lines changed: 68 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -199,64 +199,87 @@ pub fn pedersen_commitment_dleq<G: PrimeGroup, R: RngCore>(
199199
(instance, witness_vec)
200200
}
201201

202-
/// Test that a Pedersen commitment is between 0 and 1337.
202+
/// Test that a Pedersen commitment is in the given range.
203203
#[allow(non_snake_case)]
204-
pub fn test_range<G: PrimeGroup, R: RngCore>(
204+
pub fn range_instance_generation<G: PrimeGroup, R: RngCore>(
205205
mut rng: &mut R,
206+
input: u64,
207+
range: std::ops::Range<u64>,
206208
) -> (CanonicalLinearRelation<G>, Vec<G::Scalar>) {
207209
let G = G::generator();
208210
let H = G::random(&mut rng);
209211

210-
let bases = [1, 2, 4, 8, 16, 32, 64, 128, 256, 313, 512].map(G::Scalar::from);
211-
const BITS: usize = 11;
212+
let delta = range.end - range.start;
213+
let whole_bits = (delta - 1).ilog2() as usize;
214+
let remainder = delta - (1 << whole_bits);
215+
216+
// Compute the bases used to express the input as a linear combination of the bit decomposition
217+
// of the input.
218+
let mut bases = (0..whole_bits).map(|i| 1 << i).collect::<Vec<_>>();
219+
bases.push(remainder);
220+
assert_eq!(range.start + bases.iter().sum::<u64>(), range.end - 1);
212221

213222
let mut instance = LinearRelation::new();
214223
let [var_G, var_H] = instance.allocate_elements();
215224
let [var_x, var_r] = instance.allocate_scalars();
216-
let vars_b = instance.allocate_scalars::<BITS>();
217-
let vars_s = instance.allocate_scalars::<BITS>();
218-
let var_s2 = instance.allocate_scalars::<BITS>();
219-
let var_Ds = instance.allocate_elements::<BITS>();
225+
let vars_b = instance.allocate_scalars_vec(bases.len());
226+
let vars_s = instance.allocate_scalars_vec(bases.len() - 1);
227+
let var_s2 = instance.allocate_scalars_vec(bases.len());
228+
let var_Ds = instance.allocate_elements_vec(bases.len());
220229

221-
// `var_Ds[i]` are bit commitments.
222-
for i in 0..BITS {
230+
// `var_C` is a Pedersen commitment to `var_x`.
231+
let var_C = instance.allocate_eq(var_x * var_G + var_r * var_H);
232+
// `var_Ds[i]` are bit commitments...
233+
for i in 1..bases.len() {
223234
instance.append_equation(var_Ds[i], vars_b[i] * var_G + vars_s[i] * var_H);
224235
instance.append_equation(var_Ds[i], vars_b[i] * var_Ds[i] + var_s2[i] * var_H);
225236
}
226-
// `var_C` is a Pedersen commitment to `var_x`.
227-
let var_C = instance.allocate_eq(var_x * var_G + var_r * var_H);
228-
// `var_x` = sum(bases[i] * var_b[i])
229-
// This equation is "trivial", in that it does not contain any scalar var.
230-
// Our linear relation is smart enough to check this outside of the proof,
231-
// which is what a normal implementation would do.
237+
// ... satisfying that sum(Ds[i] * bases[i]) = C
232238
instance.append_equation(
233-
var_C,
234-
(0..BITS).map(|i| var_Ds[i] * bases[i]).sum::<Sum<_>>(),
239+
var_Ds[0],
240+
var_C
241+
- var_G * G::Scalar::from(range.start)
242+
- (1..bases.len())
243+
.map(|i| var_Ds[i] * G::Scalar::from(bases[i]))
244+
.sum::<Sum<_>>(),
235245
);
246+
instance.append_equation(var_Ds[0], vars_b[0] * var_Ds[0] + var_s2[0] * var_H);
236247

248+
// Compute the witness
237249
let r = G::Scalar::random(&mut rng);
238-
let x = G::Scalar::from(822);
239-
240-
let b = [
241-
G::Scalar::ZERO,
242-
G::Scalar::ONE,
243-
G::Scalar::ONE,
244-
G::Scalar::ZERO,
245-
G::Scalar::ONE,
246-
G::Scalar::ONE,
247-
G::Scalar::ZERO,
248-
G::Scalar::ZERO,
249-
G::Scalar::ONE,
250-
G::Scalar::ZERO,
251-
G::Scalar::ONE,
252-
];
250+
let x = G::Scalar::from(input);
251+
252+
// IMPORTANT: this segment of the witness generation is NOT constant-time.
253+
// See PR #80 for details.
254+
let b = {
255+
let mut rest = input - range.start;
256+
let mut b = vec![G::Scalar::ZERO; bases.len()];
257+
assert!(rest < delta);
258+
for (i, &base) in bases.iter().enumerate().rev() {
259+
if rest >= base {
260+
b[i] = G::Scalar::ONE;
261+
rest -= base;
262+
}
263+
}
264+
265+
b
266+
};
267+
assert_eq!(
268+
x,
269+
G::Scalar::from(range.start)
270+
+ (0..bases.len())
271+
.map(|i| G::Scalar::from(bases[i]) * b[i])
272+
.sum::<G::Scalar>()
273+
);
253274
// set the randomness for the bit decomposition
254-
let mut s = (0..BITS)
275+
let mut s = (0..bases.len())
255276
.map(|_| G::Scalar::random(&mut rng))
256277
.collect::<Vec<_>>();
257-
let partial_sum = (1..BITS).map(|i| bases[i] * s[i]).sum::<G::Scalar>();
278+
let partial_sum = (1..bases.len())
279+
.map(|i| G::Scalar::from(bases[i]) * s[i])
280+
.sum::<G::Scalar>();
258281
s[0] = r - partial_sum;
259-
let s2 = (0..BITS)
282+
let s2 = (0..bases.len())
260283
.map(|i| (G::Scalar::ONE - b[i]) * s[i])
261284
.collect::<Vec<_>>();
262285
let witness = [x, r]
@@ -269,13 +292,21 @@ pub fn test_range<G: PrimeGroup, R: RngCore>(
269292

270293
instance.set_elements([(var_G, G), (var_H, H)]);
271294
instance.set_element(var_C, G * x + H * r);
272-
for i in 0..BITS {
295+
for i in 0..bases.len() {
273296
instance.set_element(var_Ds[i], G * b[i] + H * s[i]);
274297
}
275298

276299
(instance.canonical().unwrap(), witness)
277300
}
278301

302+
/// Test that a Pedersen commitment is in `[0, bound)` for any `bound >= 0`.
303+
#[allow(non_snake_case)]
304+
pub fn test_range<G: PrimeGroup, R: RngCore>(
305+
mut rng: &mut R,
306+
) -> (CanonicalLinearRelation<G>, Vec<G::Scalar>) {
307+
range_instance_generation(&mut rng, 822, 0..1337)
308+
}
309+
279310
/// LinearMap for knowledge of an opening for use in a BBS commitment.
280311
// BBS message length is 3
281312
#[allow(non_snake_case)]

0 commit comments

Comments
 (0)