Skip to content

Commit 722d18e

Browse files
committed
opt: change polynomials to the point form to eliminate elliptic curve operations
1 parent f7ea44e commit 722d18e

File tree

1 file changed

+144
-65
lines changed

1 file changed

+144
-65
lines changed

src/cac/vsss.rs

Lines changed: 144 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -57,30 +57,125 @@ impl Secp256k1 {
5757
}
5858
}
5959

60-
// we use this for both polynomials over scalars and over projective points
60+
fn precalculated_factorials_and_inverses(n: usize) -> (Vec<Fr>, Vec<Fr>, Vec<Fr>) {
61+
let factorial: Vec<Fr> = std::iter::once(Fr::one())
62+
.chain((1..n).scan(Fr::one(), |state, i| {
63+
*state *= Fr::from(i as u64);
64+
Some(*state)
65+
}))
66+
.collect();
67+
68+
// inv_fact[i] = 1 / factorial[i]
69+
let inv_factorial: Vec<Fr> = (0..n)
70+
.rev()
71+
.scan(
72+
factorial[n - 1]
73+
.inverse()
74+
.expect("This is guaranteed to be non-zero"),
75+
|cur_state, i| {
76+
let ith_value = *cur_state;
77+
*cur_state *= Fr::from(i as u64);
78+
Some(ith_value)
79+
},
80+
)
81+
.collect::<Vec<_>>()
82+
.into_iter()
83+
.rev()
84+
.collect();
85+
86+
let inv: Vec<Fr> = (0..n)
87+
.map(|i| {
88+
if i == 0 {
89+
Fr::zero() //This should never be used
90+
} else {
91+
inv_factorial[i] * factorial[i - 1]
92+
}
93+
})
94+
.collect();
95+
96+
(factorial, inv_factorial, inv)
97+
}
98+
99+
// This polynomial is in the (point, value) form instead of coefficient form (points are integers in range [0, degree] converted to Fr's)
100+
// It's used both for polynomials with scalar and projective point domains
61101
#[derive(Clone, Debug, Serialize, Deserialize)]
62102
pub struct Polynomial<T>(Vec<T>);
63103

64104
impl<T> Polynomial<T>
65105
where
66-
for<'a> T: Add<T, Output = T> + Mul<&'a Fr, Output = T> + Clone,
106+
for<'a> T: Add<T, Output = T> + Mul<&'a Fr, Output = T> + Clone + Zero,
67107
{
68-
// todo: max x an int
69-
fn eval_at(&self, x: Fr) -> T {
70-
// Horner's method
71-
let mut iter = self.0.iter().rev();
72-
let mut acc = iter
73-
.next()
74-
.expect("polynomial must have at least one coefficient")
75-
.clone();
76-
for coeff in iter {
77-
acc = coeff.clone() + acc * &x;
108+
#[allow(dead_code)]
109+
// naive lagrange interpolation, used for testing
110+
fn eval_at(&self, x: usize) -> T {
111+
if x < self.0.len() {
112+
return self.0[x].clone();
78113
}
79-
acc
114+
115+
let x_fr = Fr::from(x as u32);
116+
self.0
117+
.iter()
118+
.enumerate()
119+
.fold(T::zero(), |result, (i, y_i)| {
120+
let x_i = Fr::from(i as u64);
121+
// Compute L_i(x)
122+
let (num, denum) = self.0.iter().enumerate().filter(|(j, _)| *j != i).fold(
123+
(Fr::one(), Fr::one()),
124+
|(num, denum), (j, _)| {
125+
let x_j = Fr::from(j as u64);
126+
(num * (x_fr - x_j), denum * (x_i - x_j))
127+
},
128+
);
129+
130+
// calculate li = num / denum = num * denum^{-1}
131+
let denum_inv = denum.inverse().expect("x_i - x_j must be nonzero");
132+
let li = num * denum_inv;
133+
134+
result + y_i.clone() * &li
135+
})
136+
}
137+
138+
/// evaluates the function at smallest consecutive integer points bigger than the degree
139+
/// functions similar to [`lagrange_interpolate_whole_polynomial`],
140+
fn eval_at_suffix_points(&self, n_points: usize) -> Vec<T> {
141+
let n_known = self.0.len();
142+
let n = n_known + n_points;
143+
let (factorial, inv_factorial, inv) = precalculated_factorials_and_inverses(n);
144+
145+
// For x, calculates the multiplication of (x - i) for all i in known_points (known_points = 0..=degree)
146+
// returns the inverse of the multiplication result, if its one of the known points
147+
let get_coeff = |x: usize| {
148+
if x < n_known {
149+
//inverse
150+
let mut result = inv_factorial[x] * inv_factorial[n_known - 1 - x];
151+
if (n_known - x).is_multiple_of(2) {
152+
result *= -Fr::one();
153+
}
154+
result
155+
} else {
156+
factorial[x] * inv_factorial[x - n_known]
157+
}
158+
};
159+
160+
let lagrange_basis_polynomial_coeffs: Vec<Fr> = (0..n_known).map(&get_coeff).collect();
161+
162+
(n_known..n_known + n_points)
163+
.map(|x| {
164+
let mut result = T::zero();
165+
let nom_coeff = get_coeff(x);
166+
for i in 0..n_known {
167+
let whole_coeff: Fr =
168+
lagrange_basis_polynomial_coeffs[i] * inv[x - i] * nom_coeff;
169+
result = result + self.0[i].clone() * &whole_coeff;
170+
}
171+
result
172+
})
173+
.collect()
80174
}
81175
}
82176

83177
impl Polynomial<Fr> {
178+
// Generate with points with Fr coordinates in the range [0, degree] for efficient commitment
84179
pub fn rand(mut rand: impl Rng, degree: usize) -> Self {
85180
Self((0..degree + 1).map(|_| Fr::rand(&mut rand)).collect())
86181
}
@@ -89,11 +184,17 @@ impl Polynomial<Fr> {
89184
PolynomialCommits(Polynomial(secp.generator_batch_mul(&self.0)))
90185
}
91186

92-
// shares are return with 0-based index. However, we evaluate share i at
93-
// x = i+1, since the value at x=0 represents the secret
94187
pub fn shares(&self, num_shares: usize) -> Vec<(usize, Fr)> {
188+
let n_known = self.0.len();
189+
let suffix_points = self.eval_at_suffix_points(num_shares - n_known);
95190
(0..num_shares)
96-
.map(|i| (i, self.eval_at(Fr::from((i + 1) as u64))))
191+
.map(|i| {
192+
if i < n_known {
193+
(i, self.0[i])
194+
} else {
195+
(i, suffix_points[i - n_known])
196+
}
197+
})
97198
.collect()
98199
}
99200

@@ -116,9 +217,15 @@ pub struct ShareCommits(pub Vec<Projective>);
116217

117218
impl ShareCommits {
118219
pub fn verify(&self, polynomial_commits: &PolynomialCommits) -> Result<(), String> {
220+
let n_known = polynomial_commits.0.0.len();
221+
let n_unknown = self.0.len() - n_known;
222+
let unknown_points = polynomial_commits.0.eval_at_suffix_points(n_unknown);
119223
for (i, share_commit) in self.0.iter().enumerate() {
120-
let recomputed_share_commit = polynomial_commits.0.eval_at(Fr::from((i + 1) as u64));
121-
224+
let recomputed_share_commit = if i < n_known {
225+
polynomial_commits.0.0[i]
226+
} else {
227+
unknown_points[i - n_known]
228+
};
122229
if share_commit != &recomputed_share_commit {
123230
return Err("Share commit verification failed".to_owned());
124231
}
@@ -161,40 +268,7 @@ pub fn lagrange_interpolate_whole_polynomial(
161268
assert!(!known_points.is_empty() || !missing_points.is_empty());
162269

163270
let n = known_points.len() + missing_points.len();
164-
let factorial: Vec<Fr> = std::iter::once(Fr::one())
165-
.chain((1..n).scan(Fr::one(), |state, i| {
166-
*state *= Fr::from(i as u64);
167-
Some(*state)
168-
}))
169-
.collect();
170-
171-
// inv_fact[i] = 1 / factorial[i]
172-
let inv_factorial: Vec<Fr> = (0..n)
173-
.rev()
174-
.scan(
175-
factorial[n - 1]
176-
.inverse()
177-
.expect("This is guaranteed to be non-zero"),
178-
|cur_state, i| {
179-
let ith_value = *cur_state;
180-
*cur_state *= Fr::from(i as u64);
181-
Some(ith_value)
182-
},
183-
)
184-
.collect::<Vec<_>>()
185-
.into_iter()
186-
.rev()
187-
.collect();
188-
189-
let inv: Vec<Fr> = (0..n)
190-
.map(|i| {
191-
if i == 0 {
192-
Fr::zero() //This should never be used
193-
} else {
194-
inv_factorial[i] * factorial[i - 1]
195-
}
196-
})
197-
.collect();
271+
let (factorial, inv_factorial, inv) = precalculated_factorials_and_inverses(n);
198272

199273
// For x, calculates the multiplication of (x - i) for all i in known_points (known_points = 0..n \ missing_points)
200274
// returns the inverse of the multiplication result, based on the parameter
@@ -252,18 +326,6 @@ mod tests {
252326
use rand::{SeedableRng, seq::index::sample};
253327
use rand_chacha::ChaCha20Rng;
254328
use std::collections::HashSet;
255-
#[test]
256-
fn test_polynomial_eval() {
257-
let polynomial = Polynomial::<Fr>::rand(rand::thread_rng(), 2);
258-
259-
match polynomial.0.as_slice() {
260-
&[a, b, c] => {
261-
let x = Fr::rand(&mut rand::thread_rng());
262-
assert_eq!(polynomial.eval_at(x), a + b * x + c * x * x);
263-
}
264-
_ => unreachable!(),
265-
}
266-
}
267329

268330
#[test]
269331
fn test_commit_verification() {
@@ -324,7 +386,7 @@ mod tests {
324386
.map(|x| x + 1)
325387
.collect::<Vec<_>>();
326388
let polynomial = Polynomial::rand(seed_rng, n_revealed - 1);
327-
let points = polynomial.shares(n_total); //points[i].0 = i
389+
let points = polynomial.shares(n_total);
328390

329391
let aux_set: HashSet<_> = hidden_points.iter().copied().collect();
330392
let known_points: Vec<(usize, Fr)> = points
@@ -336,6 +398,23 @@ mod tests {
336398

337399
for (x, y) in hidden_points.into_iter().zip(answer.into_iter()) {
338400
assert_eq!(points[x].1, y);
401+
assert_eq!(polynomial.eval_at(x), y);
402+
}
403+
}
404+
}
405+
406+
#[test]
407+
fn test_suffix_interpolation() {
408+
for (n_revealed, n_hidden) in vec![(5usize, 2usize), (100, 10), (175, 7)] {
409+
// Assumes one of the revealed ones is 0, as it will be in application, includes it in the n_revealed ones
410+
let n_total = n_revealed + n_hidden;
411+
let seed_rng = ChaCha20Rng::seed_from_u64(42);
412+
let polynomial = Polynomial::rand(seed_rng, n_revealed - 1);
413+
let points = polynomial.shares(n_total);
414+
let answer = polynomial.eval_at_suffix_points(n_hidden);
415+
for (x, y) in (n_revealed..n_total).into_iter().zip(answer.into_iter()) {
416+
assert_eq!(points[x].1, y);
417+
assert_eq!(polynomial.eval_at(x), y);
339418
}
340419
}
341420
}

0 commit comments

Comments
 (0)