Skip to content

Commit 5e35055

Browse files
authored
Optimize canonical linear relation constraint processing (#98)
1 parent e2cc260 commit 5e35055

File tree

3 files changed

+49
-72
lines changed

3 files changed

+49
-72
lines changed

src/group/serialization.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ pub fn group_elt_serialized_len<G: PrimeGroup>() -> usize {
2222
///
2323
/// # Returns
2424
/// - A `Vec<u8>` containing the concatenated canonical compressed byte representations.
25-
pub fn serialize_elements<G: PrimeGroup>(elements: &[G]) -> Vec<u8> {
25+
pub fn serialize_elements<'a, G: PrimeGroup>(elements: impl IntoIterator<Item = &'a G>) -> Vec<u8> {
2626
let mut bytes = Vec::new();
2727
for element in elements {
2828
bytes.extend_from_slice(element.to_bytes().as_ref());

src/linear_relation/canonical.rs

Lines changed: 42 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
#[cfg(not(feature = "std"))]
22
use ahash::RandomState;
3-
use alloc::boxed::Box;
43
use alloc::format;
54
use alloc::vec::Vec;
65
use core::iter;
@@ -17,6 +16,7 @@ use subtle::{Choice, ConstantTimeEq};
1716
use super::{GroupMap, GroupVar, LinearCombination, LinearRelation, ScalarTerm, ScalarVar};
1817
use crate::errors::{Error, InvalidInstance};
1918
use crate::group::msm::VariableMultiScalarMul;
19+
use crate::serialization::serialize_elements;
2020

2121
// XXX. this definition is uncomfortably similar to LinearRelation, exception made for the weights.
2222
// It'd be nice to better compress potentially duplicated code.
@@ -28,7 +28,7 @@ use crate::group::msm::VariableMultiScalarMul;
2828
#[derive(Clone, Debug, Default)]
2929
pub struct CanonicalLinearRelation<G: PrimeGroup> {
3030
/// The image group elements (left-hand side of equations)
31-
pub image: Vec<G>,
31+
pub image: Vec<GroupVar<G>>,
3232
/// The constraints, where each constraint is a vector of (scalar_var, group_var) pairs
3333
/// representing the right-hand side of the equation
3434
pub linear_combinations: Vec<Vec<(ScalarVar<G>, GroupVar<G>)>>,
@@ -107,8 +107,12 @@ impl<G: PrimeGroup> CanonicalLinearRelation<G> {
107107
}
108108

109109
// Create new weighted group element
110+
// Use a special case for one, as this is the most common weight.
110111
let original_group_val = original_group_elements.get(group_var)?;
111-
let weighted_group = original_group_val * weight;
112+
let weighted_group = match *weight == G::Scalar::ONE {
113+
true => original_group_val,
114+
false => original_group_val * weight,
115+
};
112116

113117
// Add to our group elements with new index (length)
114118
let new_var = self.group_elements.push(weighted_group);
@@ -135,7 +139,7 @@ impl<G: PrimeGroup> CanonicalLinearRelation<G> {
135139
let group_var = weighted_term.term.elem;
136140
let weight = &weighted_term.weight;
137141

138-
if weight.is_zero().into() {
142+
if weight.is_zero_vartime() {
139143
continue; // Skip zero weights
140144
}
141145

@@ -172,7 +176,8 @@ impl<G: PrimeGroup> CanonicalLinearRelation<G> {
172176
));
173177
}
174178

175-
self.image.push(canonical_image);
179+
let canonical_image_group_var = self.group_elements.push(canonical_image);
180+
self.image.push(canonical_image_group_var);
176181
self.linear_combinations.push(rhs_terms);
177182

178183
Ok(())
@@ -191,52 +196,18 @@ impl<G: PrimeGroup> CanonicalLinearRelation<G> {
191196
pub fn label(&self) -> Vec<u8> {
192197
let mut out = Vec::new();
193198

194-
// Create an ordered list of unique group element representations. Elements are ordered
195-
// based on the order they appear in the canonical linear relation, as seen by the loop
196-
// below.
197-
// Order in this list is expected to be stable and lead to the same vector string.
198-
// However, relations built using TryFrom<LinearRelation> are NOT guaranteed to lead
199-
// to the same ordering of elements across versions of this library.
200-
// Changes to LinearRelation may have unpredictable effects on how this label is built.
201-
#[cfg(feature = "std")]
202-
let mut group_repr_mapping: HashMap<Box<[u8]>, u32> = HashMap::new();
203-
#[cfg(not(feature = "std"))]
204-
let mut group_repr_mapping: HashMap<Box<[u8]>, u32, RandomState> =
205-
HashMap::with_hasher(RandomState::new());
206-
let mut group_elements_ordered = Vec::new();
207-
208-
// Helper function to get or create index for a group element representation
209-
let mut repr_index = |elem_repr: G::Repr| -> u32 {
210-
if let Some(&index) = group_repr_mapping.get(elem_repr.as_ref()) {
211-
return index;
212-
}
213-
214-
let new_index = group_elements_ordered.len() as u32;
215-
group_elements_ordered.push(elem_repr);
216-
group_repr_mapping.insert(elem_repr.as_ref().into(), new_index);
217-
new_index
218-
};
219-
220199
// Build constraint data in the same order as original, as a nested list of group and
221200
// scalar indices. Note that the group indices are into group_elements_ordered.
222201
let mut constraint_data = Vec::<(u32, Vec<(u32, u32)>)>::new();
223202

224-
for (image_elem, constraint_terms) in iter::zip(&self.image, &self.linear_combinations) {
225-
// First, add the left-hand side (image) element
226-
let lhs_index = repr_index(image_elem.to_bytes());
227-
203+
for (image_var, constraint_terms) in iter::zip(&self.image, &self.linear_combinations) {
228204
// Build the RHS terms
229205
let mut rhs_terms = Vec::new();
230206
for (scalar_var, group_var) in constraint_terms {
231-
let group_elem = self
232-
.group_elements
233-
.get(*group_var)
234-
.expect("Group element not found");
235-
let group_index = repr_index(group_elem.to_bytes());
236-
rhs_terms.push((scalar_var.0 as u32, group_index));
207+
rhs_terms.push((scalar_var.0 as u32, group_var.0 as u32));
237208
}
238209

239-
constraint_data.push((lhs_index, rhs_terms));
210+
constraint_data.push((image_var.0 as u32, rhs_terms));
240211
}
241212

242213
// 1. Number of equations
@@ -258,10 +229,13 @@ impl<G: PrimeGroup> CanonicalLinearRelation<G> {
258229
}
259230
}
260231

261-
// Dump the group elements in the order they were first encountered
262-
for elem_repr in group_elements_ordered {
263-
out.extend_from_slice(elem_repr.as_ref());
264-
}
232+
// Dump the group elements.
233+
let group_reprs = serialize_elements(
234+
self.group_elements
235+
.iter()
236+
.map(|(_, elem)| elem.expect("expected group variable to be assigned")),
237+
);
238+
out.extend_from_slice(&group_reprs);
265239

266240
out
267241
}
@@ -410,9 +384,7 @@ impl<G: PrimeGroup> CanonicalLinearRelation<G> {
410384
// Build constraints
411385
for (lhs_index, rhs_terms) in constraint_data {
412386
// Add image element
413-
canonical
414-
.image
415-
.push(group_elements_ordered[lhs_index as usize]);
387+
canonical.image.push(group_var_map[lhs_index as usize]);
416388

417389
// Build linear combination
418390
let mut linear_combination = Vec::new();
@@ -426,6 +398,16 @@ impl<G: PrimeGroup> CanonicalLinearRelation<G> {
426398

427399
Ok(canonical)
428400
}
401+
402+
/// Access the group elements associated with the image (i.e. left-hand side), panicking if any
403+
/// of the image variables are unassigned in the group mkap.
404+
pub(crate) fn image_elements(&self) -> impl Iterator<Item = G> + use<'_, G> {
405+
self.image.iter().map(|var| {
406+
self.group_elements
407+
.get(*var)
408+
.expect("expected group variable to be assigned")
409+
})
410+
}
429411
}
430412

431413
impl<G: PrimeGroup> TryFrom<LinearRelation<G>> for CanonicalLinearRelation<G> {
@@ -460,23 +442,20 @@ impl<G: PrimeGroup> TryFrom<&LinearRelation<G>> for CanonicalLinearRelation<G> {
460442
// If any group element in the image is not assigned, return `InvalidInstance`.
461443
let lhs_value = relation.linear_map.group_elements.get(*lhs)?;
462444

463-
// If any group element in the linear constraints is not assigned, return `InvalidInstance`.
464-
let rhs_elements = rhs
465-
.0
466-
.iter()
467-
.map(|weighted| relation.linear_map.group_elements.get(weighted.term.elem))
468-
.collect::<Result<Vec<G>, _>>()?;
469-
470445
// Compute the constant terms on the right-hand side of the equation.
471-
let rhs_constants = rhs
446+
// If any group element in the linear constraints is not assigned, return `InvalidInstance`.
447+
let rhs_constant_terms = rhs
472448
.0
473449
.iter()
474-
.map(|element| match element.term.scalar {
475-
ScalarTerm::Unit => element.weight,
476-
_ => G::Scalar::ZERO,
450+
.filter(|term| matches!(term.term.scalar, ScalarTerm::Unit))
451+
.map(|term| {
452+
let elem = relation.linear_map.group_elements.get(term.term.elem)?;
453+
let scalar = term.weight;
454+
Ok((elem, scalar))
477455
})
478-
.collect::<Vec<_>>();
479-
let rhs_constant_term = G::msm(&rhs_constants, &rhs_elements);
456+
.collect::<Result<(Vec<G>, Vec<G::Scalar>), _>>()?;
457+
458+
let rhs_constant_term = G::msm(&rhs_constant_terms.1, &rhs_constant_terms.0);
480459

481460
// We say that an equation is trivial if it contains no scalar variables.
482461
// To "contain no scalar variables" means that each term in the right-hand side is a unit or its weight is zero.
@@ -516,8 +495,7 @@ impl<G: PrimeGroup + ConstantTimeEq> CanonicalLinearRelation<G> {
516495
/// If the number of scalars is more than the number of scalar variables, the extra elements are ignored.
517496
pub fn is_witness_valid(&self, witness: &[G::Scalar]) -> Choice {
518497
let got = self.evaluate(witness);
519-
self.image
520-
.iter()
498+
self.image_elements()
521499
.zip(got)
522500
.fold(Choice::from(1), |acc, (lhs, rhs)| acc & lhs.ct_eq(&rhs))
523501
}

src/schnorr_protocol.rs

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -54,10 +54,9 @@ impl<G: PrimeGroup> SigmaProtocol for CanonicalLinearRelation<G> {
5454
// If the image is the identity, then the relation must be
5555
// trivial, or else the proof will be unsound
5656
if self
57-
.image
58-
.iter()
57+
.image_elements()
5958
.zip(self.linear_combinations.iter())
60-
.any(|(&x, c)| x == G::identity() && !c.is_empty())
59+
.any(|(x, c)| x == G::identity() && !c.is_empty())
6160
{
6261
return Err(Error::InvalidInstanceWitnessPair);
6362
}
@@ -124,8 +123,8 @@ impl<G: PrimeGroup> SigmaProtocol for CanonicalLinearRelation<G> {
124123

125124
let lhs = self.evaluate(response);
126125
let mut rhs = Vec::new();
127-
for (i, g) in commitment.iter().enumerate() {
128-
rhs.push(self.image[i] * challenge + g);
126+
for (img, g) in self.image_elements().zip(commitment) {
127+
rhs.push(img * challenge + g);
129128
}
130129
if lhs == rhs {
131130
Ok(())
@@ -298,8 +297,8 @@ where
298297
let response_image = self.evaluate(response);
299298
let commitment = response_image
300299
.iter()
301-
.zip(&self.image)
302-
.map(|(res, img)| *res - *img * challenge)
300+
.zip(self.image_elements())
301+
.map(|(res, img)| *res - img * challenge)
303302
.collect::<Vec<_>>();
304303
Ok(commitment)
305304
}

0 commit comments

Comments
 (0)