generic_ec_zkp/polynomial.rs
1//! Provides [polynomial](Polynomial) primitive, typically used in secret sharing and threshold DKG
2
3#[cfg(feature = "alloc")]
4#[doc(inline)]
5pub use requires_alloc::*;
6
7#[cfg(feature = "alloc")]
8#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
9mod requires_alloc {
10 use alloc::{vec, vec::Vec};
11 use core::{iter, ops};
12
13 use generic_ec::traits::{IsZero, Samplable, Zero};
14 use rand_core::RngCore;
15
16 /// Polynomial $f(x) = \sum_i a_i x^i$ defined as a list of coefficients $[a_0, \dots, a_{\text{degree}}]$
17 ///
18 /// Polynomial is generic over type of coefficients `C`, it can be `Scalar<E>`, `NonZero<Scalar<E>>`, `SecretScalar<E>`, `Point<E>`,
19 /// or any other type that implements necessary traits.
20 #[derive(Debug, Clone)]
21 #[cfg_attr(feature = "udigest", derive(udigest::Digestable))]
22 pub struct Polynomial<C> {
23 /// `coefs[i]` is coefficient of `x^i` term
24 ///
25 /// Last element of `coefs` must be non-zero
26 coefs: Vec<C>,
27 }
28
29 impl<C: IsZero> Polynomial<C> {
30 /// Constructs a polynomial from its coefficients
31 ///
32 /// `coefs[i]` is coefficient of `x^i` term. Resulting polynomial will be
33 /// $f(x) = \sum_i \\text{coefs}_i \cdot x^i$
34 ///
35 /// ## Example
36 /// ```rust
37 /// # use rand_core::OsRng;
38 /// use generic_ec::{Scalar, curves::Secp256k1};
39 /// use generic_ec_zkp::polynomial::Polynomial;
40 ///
41 /// let coefs: [Scalar<Secp256k1>; 3] = [
42 /// Scalar::random(&mut OsRng),
43 /// Scalar::random(&mut OsRng),
44 /// Scalar::random(&mut OsRng),
45 /// ];
46 /// let polynomial = Polynomial::from_coefs(coefs.to_vec());
47 ///
48 /// let x = Scalar::random(&mut OsRng);
49 /// assert_eq!(
50 /// coefs[0] + x * coefs[1] + x * x * coefs[2],
51 /// polynomial.value::<_, Scalar<_>>(&x),
52 /// );
53 /// ```
54 pub fn from_coefs(mut coefs: Vec<C>) -> Self {
55 // Truncate trailing zeroes
56 let zeroes_count = coefs
57 .iter()
58 .rev()
59 .take_while(|coef_i| coef_i.is_zero())
60 .count();
61 let coefs_len = coefs.len();
62 coefs.truncate(coefs_len - zeroes_count);
63
64 Self { coefs }
65 }
66 }
67
68 impl<C> Polynomial<C> {
69 /// Returns polynomial degree
70 ///
71 /// Polynomial degree is index of most significant non-zero coefficient. Polynomial $f(x) = 0$
72 /// considered to have degree $deg(f) = 0$.
73 ///
74 /// $$
75 /// \begin{dcases}
76 /// deg(f(x) = \sum_i a_i \cdot x^i) &= n \\text{, where } a_n \ne 0 \land n \to max \\\\
77 /// deg(f(x) = 0) &= 0
78 /// \end{dcases}
79 /// $$
80 pub fn degree(&self) -> usize {
81 #[allow(clippy::expect_used)]
82 match self.coefs.len() {
83 0 => 0,
84 len => len - 1,
85 }
86 }
87
88 /// Returns polynomial coefficients
89 pub fn coefs(&self) -> &[C] {
90 &self.coefs
91 }
92
93 /// Destructs polynomial, returns its coefficients
94 pub fn into_coefs(self) -> Vec<C> {
95 self.coefs
96 }
97 }
98
99 impl<C: Samplable> Polynomial<C> {
100 /// Samples a random polynomial with specified degree
101 pub fn sample(rng: &mut impl RngCore, degree: usize) -> Self {
102 Self {
103 coefs: iter::repeat_with(|| C::random(rng))
104 .take(degree + 1)
105 .collect(),
106 }
107 }
108
109 /// Samples a random polynomial with specified degree and constant term
110 ///
111 /// Constant term determines value of polynomial at point zero: $f(0) = \\text{const\\_term}$
112 ///
113 /// ## Example
114 /// ```rust
115 /// use generic_ec::{Scalar, curves::Secp256k1};
116 /// use generic_ec_zkp::polynomial::Polynomial;
117 /// # use rand_core::OsRng;
118 ///
119 /// let const_term = Scalar::<Secp256k1>::from(1234);
120 /// let polynomial = Polynomial::sample_with_const_term(&mut OsRng, 3, const_term);
121 /// assert_eq!(const_term, polynomial.value::<_, Scalar<_>>(&Scalar::zero()));
122 /// ```
123 pub fn sample_with_const_term(
124 rng: &mut impl RngCore,
125 degree: usize,
126 const_term: C,
127 ) -> Self {
128 Self {
129 coefs: iter::once(const_term)
130 .chain(iter::repeat_with(|| C::random(rng)).take(degree))
131 .collect(),
132 }
133 }
134 }
135
136 impl<C> Polynomial<C> {
137 /// Evaluates polynomial value at given point: $f(\\text{point})$
138 ///
139 /// Polynomial coefficients, point, and output can all be differently typed.
140 ///
141 /// ## Example: polynomial with coefficients typed as non-zero scalars vs elliptic points
142 /// Let $f(x) = a_1 \cdot x + a_0$ and $F(x) = G \cdot f(x)$. Coefficients
143 /// of $f(x)$ are typed as `NonZero<Scalar<E>>`, and $F(x)$ has coefficients typed as `NonZero<Point<E>>`.
144 ///
145 /// When we evaluate $f(x)$, we have coefficients `C` of type `NonZero<Scalar<E>>`, and both point `P`
146 /// and output `O` of type `Scalar<E>`.
147 ///
148 /// On other hand, when $F(x)$ is evaluated, coefficients `C` have type `NonZero<Point<E>>`, point `P` has
149 /// type `Scalar<E>`, and output `O` is of type `Point<E>`.
150 ///
151 /// ```rust
152 /// use generic_ec::{Point, Scalar, NonZero, curves::Secp256k1};
153 /// use generic_ec_zkp::polynomial::Polynomial;
154 /// # use rand_core::OsRng;
155 ///
156 /// let f: Polynomial<NonZero<Scalar<Secp256k1>>> = Polynomial::sample(&mut OsRng, 1);
157 /// let F: Polynomial<NonZero<Point<_>>> = &f * &Point::generator();
158 ///
159 /// let x = Scalar::random(&mut OsRng);
160 /// assert_eq!(
161 /// f.value::<_, Scalar<_>>(&x) * Point::generator(),
162 /// F.value::<_, Point<_>>(&x)
163 /// );
164 /// ```
165 pub fn value<P, O>(&self, point: &P) -> O
166 where
167 O: Zero,
168 for<'a> O: ops::Mul<&'a P, Output = O> + ops::Add<&'a C, Output = O>,
169 {
170 self.coefs
171 .iter()
172 .rev()
173 .fold(O::zero(), |acc, coef_i| acc * point + coef_i)
174 }
175 }
176
177 /// Multiplies polyinomial $F(x)$ at $k$ returning resulting polyinomial
178 /// $F'(x) = k \cdot F(x)$ without allocations
179 ///
180 /// $k$ needs to be of type `C`.
181 impl<C> ops::Mul<&C> for Polynomial<C>
182 where
183 for<'a> &'a C: ops::Mul<&'a C, Output = C>,
184 {
185 type Output = Polynomial<C>;
186
187 fn mul(mut self, rhs: &C) -> Self::Output {
188 self.coefs
189 .iter_mut()
190 .for_each(|coef_i| *coef_i = &*coef_i * rhs);
191 self
192 }
193 }
194
195 /// Multiplies polynomial $F(x)$ at $k$ returning resulting polynomial
196 /// $F'(x) = k \cdot F(x)$, resulting polynomial is allocated at heap
197 ///
198 /// $k$ can be any type as long as it can be multiplied at `C`
199 impl<B, C, O> ops::Mul<&B> for &Polynomial<C>
200 where
201 for<'a> &'a C: ops::Mul<&'a B, Output = O>,
202 {
203 type Output = Polynomial<O>;
204
205 fn mul(self, rhs: &B) -> Self::Output {
206 Polynomial {
207 coefs: self.coefs.iter().map(|coef_i| coef_i * rhs).collect(),
208 }
209 }
210 }
211
212 impl<C> ops::AddAssign<&Polynomial<C>> for Polynomial<C>
213 where
214 C: Clone + for<'a> ops::AddAssign<&'a C>,
215 {
216 fn add_assign(&mut self, rhs: &Polynomial<C>) {
217 self.coefs
218 .iter_mut()
219 .zip(&rhs.coefs)
220 .for_each(|(f1_coef_i, f2_coef_i)| *f1_coef_i += f2_coef_i);
221 if self.coefs.len() < rhs.coefs.len() {
222 let self_len = self.coefs.len();
223 self.coefs.extend_from_slice(&rhs.coefs[self_len..])
224 }
225 }
226 }
227
228 impl<C> ops::Add<&Polynomial<C>> for Polynomial<C>
229 where
230 C: Clone + for<'a> ops::AddAssign<&'a C>,
231 {
232 type Output = Polynomial<C>;
233
234 fn add(mut self, rhs: &Polynomial<C>) -> Self::Output {
235 self += rhs;
236 self
237 }
238 }
239
240 impl<'a, C> iter::Sum<&'a Polynomial<C>> for Polynomial<C>
241 where
242 C: Clone + 'a,
243 C: for<'c> ops::AddAssign<&'c C>,
244 {
245 fn sum<I: Iterator<Item = &'a Polynomial<C>>>(mut iter: I) -> Self {
246 let Some(mut sum) = iter.next().cloned() else {
247 return Self { coefs: vec![] };
248 };
249 for polynomial in iter {
250 sum += polynomial;
251 }
252 sum
253 }
254 }
255
256 impl<C> iter::Sum<Polynomial<C>> for Polynomial<C>
257 where
258 C: for<'a> ops::AddAssign<&'a C> + Clone,
259 {
260 fn sum<I: Iterator<Item = Polynomial<C>>>(mut iter: I) -> Self {
261 let Some(mut sum) = iter.next() else {
262 return Self { coefs: vec![] };
263 };
264 for polynomial in iter {
265 sum += &polynomial
266 }
267 sum
268 }
269 }
270
271 #[cfg(feature = "serde")]
272 impl<C> serde::Serialize for Polynomial<C>
273 where
274 C: serde::Serialize,
275 {
276 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
277 where
278 S: serde::Serializer,
279 {
280 self.coefs.serialize(serializer)
281 }
282 }
283
284 #[cfg(feature = "serde")]
285 impl<'de, C: IsZero> serde::Deserialize<'de> for Polynomial<C>
286 where
287 C: serde::Deserialize<'de>,
288 {
289 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
290 where
291 D: serde::Deserializer<'de>,
292 {
293 let coefs = Vec::<C>::deserialize(deserializer)?;
294 Ok(Self::from_coefs(coefs))
295 }
296 }
297}
298
299use generic_ec::{Curve, NonZero, Scalar};
300
301/// Calculates lagrange coefficient $\lambda_j$ to interpolate a polynomial at point $x$
302///
303/// Lagrange coefficient are often used to turn polynomial key shares into additive
304/// key shares.
305///
306/// ## Inputs
307///
308/// `xs` denotes the points with known values that define the polynomial. `j` is a index
309/// of element in `xs` for which lagrange coefficient is calculated. `x` is a point at
310/// which the polynomial is interpolated.
311///
312/// ## Returns
313/// Returns `None` if `j >= xs.len()` or if there's `m` such that `xs[j] == xs[m]` or
314/// `x == xs[m]`. Note that, generally, lagrange interpolation is only defined when
315/// elements in `xs` are pairwise distinct.
316///
317/// ## Example
318/// ```rust
319/// use generic_ec::{Scalar, SecretScalar, NonZero, curves::Secp256k1};
320/// use generic_ec_zkp::polynomial::{Polynomial, lagrange_coefficient};
321/// # use rand_core::OsRng;
322///
323/// let secret = SecretScalar::<Secp256k1>::random(&mut OsRng);
324/// // polynomial `f(x)` shares `f(0) = secret`
325/// let f = Polynomial::sample_with_const_term(&mut OsRng, 2, secret.clone());
326///
327/// // Publicly-known shares indexes
328/// let I = [Scalar::from(1), Scalar::from(2), Scalar::from(3)];
329/// // Secret shares
330/// let shares: [Scalar<_>; 3] = I.map(|i| f.value(&i));
331///
332/// // Reconstruct a `secret = f(0)`
333/// let lambdas = [0, 1, 2].map(|j| lagrange_coefficient(Scalar::zero(), j, &I).unwrap());
334/// let reconstructed_secret = lambdas
335/// .into_iter()
336/// .zip(shares)
337/// .map(|(lambda_i, x_i)| lambda_i * x_i)
338/// .sum::<Scalar<_>>();
339/// assert_eq!(secret.as_ref(), &reconstructed_secret);
340/// ```
341pub fn lagrange_coefficient<E: Curve>(
342 x: Scalar<E>,
343 j: usize,
344 xs: &[impl AsRef<Scalar<E>>],
345) -> Option<NonZero<Scalar<E>>> {
346 let xs_without_j = xs
347 .iter()
348 .enumerate()
349 .filter(|(i, _x_i)| *i != j)
350 .map(|(_, x_i)| x_i);
351 let nom = xs_without_j
352 .clone()
353 .map(|x_m| x - x_m.as_ref())
354 .product::<Scalar<E>>();
355
356 let x_j = xs.get(j)?.as_ref();
357 let denom = xs_without_j
358 .map(|x_m| x_j - x_m.as_ref())
359 .product::<Scalar<E>>();
360 let denom_inv = denom.invert()?;
361
362 NonZero::from_scalar(nom * denom_inv)
363}
364
365/// Calculates lagrange coefficient $\lambda_j$ to interpolate a polynomial at point $0$
366///
367/// Functionally the same as calling [`lagrange_coefficient(Scalar::zero(), j, xs)`](lagrange_coefficient),
368/// but a bit faster.
369pub fn lagrange_coefficient_at_zero<E: Curve>(
370 j: usize,
371 xs: &[impl AsRef<Scalar<E>>],
372) -> Option<NonZero<Scalar<E>>> {
373 let xs_without_j = xs
374 .iter()
375 .enumerate()
376 .filter(|(i, _x_i)| *i != j)
377 .map(|(_, x_i)| x_i.as_ref());
378 let nom = xs_without_j.clone().product::<Scalar<E>>();
379
380 let x_j = xs.get(j)?.as_ref();
381 let denom = xs_without_j.map(|x_m| x_m - x_j).product::<Scalar<E>>();
382 let denom_inv = denom.invert()?;
383
384 NonZero::from_scalar(nom * denom_inv)
385}
386
387#[cfg(all(test, feature = "alloc"))]
388#[generic_tests::define]
389#[allow(non_snake_case)]
390mod tests {
391 use alloc::vec::Vec;
392 use core::iter;
393
394 use generic_ec::{Curve, Point, Scalar, SecretScalar};
395 use rand::Rng;
396 use rand_dev::DevRng;
397
398 use crate::polynomial::lagrange_coefficient;
399
400 use super::Polynomial;
401
402 #[test]
403 fn secret_sharing<E: Curve>() {
404 let mut rng = DevRng::new();
405
406 // 1. Sample secret
407 let secret = SecretScalar::<E>::random(&mut rng);
408 let f = Polynomial::sample_with_const_term(&mut rng, 3, secret.clone());
409
410 // Chech that `f(0) = secret`
411 {
412 let f_0: Scalar<_> = f.value(&Scalar::zero());
413 assert_eq!(secret.as_ref(), &f_0);
414 }
415
416 // 2. Commit to the secret
417 // F(x) = G * f(x)
418 let F = &f * &Point::generator();
419 assert_eq!(
420 &secret * Point::generator(),
421 F.value::<_, Point<_>>(&Scalar::zero())
422 );
423
424 // 3. Derive 4 secret and public shares
425 let shares_indexes: [Scalar<E>; 4] = [
426 Scalar::from(1),
427 Scalar::from(2),
428 Scalar::from(3),
429 Scalar::from(4),
430 ];
431 let shares: [Scalar<_>; 4] = shares_indexes.map(|i| f.value(&i));
432 let public_shares = shares.map(|secret_share| Point::generator() * secret_share);
433
434 // Chech that `public_shares[i] = F(i)`
435 {
436 for (i, public_share) in (1..).zip(&public_shares) {
437 assert_eq!(public_share, &F.value::<_, Point<E>>(&Scalar::from(i)));
438 }
439 }
440
441 // 4. Reconstruct the secret
442 let lambdas =
443 [0, 1, 2, 3].map(|j| lagrange_coefficient(Scalar::zero(), j, &shares_indexes).unwrap());
444 let reconstructed_secret: Scalar<E> = lambdas
445 .into_iter()
446 .zip(shares)
447 .map(|(lambda_i, x_i)| lambda_i * x_i)
448 .sum();
449 assert_eq!(secret.as_ref(), &reconstructed_secret);
450
451 assert_eq!(
452 lambdas,
453 [0, 1, 2, 3].map(|j| super::lagrange_coefficient_at_zero(j, &shares_indexes).unwrap())
454 );
455 }
456
457 #[test]
458 fn polynomial_sum<E: Curve>() {
459 let mut rng = DevRng::new();
460
461 // Sample 10 polynomials of different degree
462 let polynomials: Vec<Polynomial<Scalar<E>>> = iter::repeat_with(|| {
463 let degree = rng.gen_range(5..15);
464 Polynomial::sample(&mut rng, degree)
465 })
466 .take(10)
467 .collect();
468
469 let polynomials_sum1: Polynomial<Scalar<E>> = polynomials.iter().sum();
470 let polynomials_sum2: Polynomial<Scalar<E>> = polynomials.iter().cloned().sum();
471
472 let point = Scalar::random(&mut rng);
473
474 let value_actual1 = polynomials_sum1.value::<_, Scalar<E>>(&point);
475 let value_actual2 = polynomials_sum2.value::<_, Scalar<E>>(&point);
476
477 let value_expected: Scalar<E> = polynomials
478 .iter()
479 .map(|f_i| f_i.value::<_, Scalar<E>>(&point))
480 .sum();
481
482 assert_eq!(value_expected, value_actual1);
483 assert_eq!(value_expected, value_actual2);
484 }
485
486 #[test]
487 fn polynomial_from_coefs<E: Curve>() {
488 let mut rng = DevRng::new();
489
490 let coefs = [
491 Scalar::<E>::random(&mut rng),
492 Scalar::random(&mut rng),
493 Scalar::random(&mut rng),
494 ];
495 let f = Polynomial::from_coefs(coefs.to_vec());
496
497 assert_eq!(f.degree(), 2);
498
499 for _ in 0..100 {
500 let x = Scalar::random(&mut rng);
501
502 let f_x: Scalar<E> = f.value(&x);
503 let expected = coefs[0] + x * coefs[1] + x * x * coefs[2];
504
505 assert_eq!(f_x, expected);
506 }
507 }
508
509 #[instantiate_tests(<generic_ec::curves::Secp256k1>)]
510 mod secp256k1 {}
511 #[instantiate_tests(<generic_ec::curves::Secp256r1>)]
512 mod secp256r1 {}
513 #[instantiate_tests(<generic_ec::curves::Stark>)]
514 mod stark {}
515}