commonware_cryptography/bls12381/primitives/poly.rs
1//! Polynomial operations over the BLS12-381 scalar field.
2//!
3//! # Warning
4//!
5//! The security of the polynomial operations is critical for the overall
6//! security of the threshold schemes. Ensure that the scalar field operations
7//! are performed over the correct field and that all elements are valid.
8
9use crate::bls12381::primitives::{
10 group::{self, Element, Scalar},
11 Error,
12};
13use bytes::{Buf, BufMut};
14use commonware_codec::{
15 varint::UInt, EncodeSize, Error as CodecError, FixedSize, Read, ReadExt, Write,
16};
17use rand::{rngs::OsRng, RngCore};
18use std::hash::Hash;
19
20/// Private polynomials are used to generate secret shares.
21pub type Private = Poly<group::Private>;
22
23/// Public polynomials represent commitments to secrets on a private polynomial.
24pub type Public = Poly<group::Public>;
25
26/// Signature polynomials are used in threshold signing (where a signature
27/// is interpolated using at least `threshold` evaluations).
28pub type Signature = Poly<group::Signature>;
29
30/// The default partial signature type (G2).
31pub type PartialSignature = Eval<group::Signature>;
32
33/// The default partial signature length (G2).
34pub const PARTIAL_SIGNATURE_LENGTH: usize = u32::SIZE + group::SIGNATURE_LENGTH;
35
36/// A polynomial evaluation at a specific index.
37#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
38pub struct Eval<C: Element> {
39 pub index: u32,
40 pub value: C,
41}
42
43impl<C: Element> Write for Eval<C> {
44 fn write(&self, buf: &mut impl BufMut) {
45 UInt(self.index).write(buf);
46 self.value.write(buf);
47 }
48}
49
50impl<C: Element> Read for Eval<C> {
51 fn read_cfg(buf: &mut impl Buf, _: &()) -> Result<Self, CodecError> {
52 let index = UInt::read(buf)?.into();
53 let value = C::read(buf)?;
54 Ok(Self { index, value })
55 }
56}
57
58impl<C: Element> EncodeSize for Eval<C> {
59 fn encode_size(&self) -> usize {
60 UInt(self.index).encode_size() + C::SIZE
61 }
62}
63
64/// A polynomial that is using a scalar for the variable x and a generic
65/// element for the coefficients.
66///
67/// The coefficients must be able to multiply the type of the variable,
68/// which is always a scalar.
69#[derive(Debug, Clone, PartialEq, Eq)]
70// Reference: https://github.com/celo-org/celo-threshold-bls-rs/blob/a714310be76620e10e8797d6637df64011926430/crates/threshold-bls/src/poly.rs#L24-L28
71pub struct Poly<C>(Vec<C>);
72
73/// Returns a new scalar polynomial of the given degree where each coefficients is
74/// sampled at random using kernel randomness.
75///
76/// In the context of secret sharing, the threshold is the degree + 1.
77pub fn new(degree: u32) -> Poly<Scalar> {
78 // Reference: https://github.com/celo-org/celo-threshold-bls-rs/blob/a714310be76620e10e8797d6637df64011926430/crates/threshold-bls/src/poly.rs#L46-L52
79 new_from(degree, &mut OsRng)
80}
81
82// Returns a new scalar polynomial of the given degree where each coefficient is
83// sampled at random from the provided RNG.
84///
85/// In the context of secret sharing, the threshold is the degree + 1.
86pub fn new_from<R: RngCore>(degree: u32, rng: &mut R) -> Poly<Scalar> {
87 // Reference: https://github.com/celo-org/celo-threshold-bls-rs/blob/a714310be76620e10e8797d6637df64011926430/crates/threshold-bls/src/poly.rs#L46-L52
88 let coeffs = (0..=degree).map(|_| Scalar::rand(rng)).collect::<Vec<_>>();
89 Poly::<Scalar>(coeffs)
90}
91
92impl<C> Poly<C> {
93 /// Creates a new polynomial from the given coefficients.
94 pub fn from(c: Vec<C>) -> Self {
95 Self(c)
96 }
97
98 /// Returns the constant term of the polynomial.
99 pub fn constant(&self) -> &C {
100 &self.0[0]
101 }
102
103 /// Returns the degree of the polynomial
104 pub fn degree(&self) -> u32 {
105 (self.0.len() - 1) as u32 // check size in deserialize, safe to cast
106 }
107
108 /// Returns the number of required shares to reconstruct the polynomial.
109 ///
110 /// This will be the threshold
111 pub fn required(&self) -> u32 {
112 self.0.len() as u32 // check size in deserialize, safe to cast
113 }
114}
115
116impl<C: Element> Poly<C> {
117 /// Commits the scalar polynomial to the group and returns a polynomial over
118 /// the group.
119 ///
120 /// This is done by multiplying each coefficient of the polynomial with the
121 /// group's generator.
122 pub fn commit(commits: Poly<Scalar>) -> Self {
123 // Reference: https://github.com/celo-org/celo-threshold-bls-rs/blob/a714310be76620e10e8797d6637df64011926430/crates/threshold-bls/src/poly.rs#L322-L340
124 let commits = commits
125 .0
126 .iter()
127 .map(|c| {
128 let mut commitment = C::one();
129 commitment.mul(c);
130 commitment
131 })
132 .collect::<Vec<C>>();
133
134 Poly::<C>::from(commits)
135 }
136
137 /// Returns a zero polynomial.
138 pub fn zero() -> Self {
139 Self::from(vec![C::zero()])
140 }
141
142 /// Returns the given coefficient at the requested index.
143 ///
144 /// It panics if the index is out of range.
145 pub fn get(&self, i: u32) -> C {
146 self.0[i as usize].clone()
147 }
148
149 /// Set the given element at the specified index.
150 ///
151 /// It panics if the index is out of range.
152 pub fn set(&mut self, index: u32, value: C) {
153 self.0[index as usize] = value;
154 }
155
156 /// Performs polynomial addition in place
157 pub fn add(&mut self, other: &Self) {
158 // Reference: https://github.com/celo-org/celo-threshold-bls-rs/blob/a714310be76620e10e8797d6637df64011926430/crates/threshold-bls/src/poly.rs#L87-L95
159
160 // if we have a smaller degree we should pad with zeros
161 if self.0.len() < other.0.len() {
162 self.0.resize(other.0.len(), C::zero())
163 }
164
165 self.0.iter_mut().zip(&other.0).for_each(|(a, b)| a.add(b))
166 }
167
168 /// Evaluates the polynomial at the specified value.
169 pub fn evaluate(&self, i: u32) -> Eval<C> {
170 // Reference: https://github.com/celo-org/celo-threshold-bls-rs/blob/a714310be76620e10e8797d6637df64011926430/crates/threshold-bls/src/poly.rs#L111-L129
171
172 // We add +1 because we must never evaluate the polynomial at its first point
173 // otherwise it reveals the "secret" value after a reshare (where the constant
174 // term is set to be the secret of the previous dealing).
175 let mut xi = Scalar::zero();
176 xi.set_int(i + 1);
177
178 // Use Horner's method to evaluate the polynomial
179 let res = self.0.iter().rev().fold(C::zero(), |mut sum, coeff| {
180 sum.mul(&xi);
181 sum.add(coeff);
182 sum
183 });
184 Eval {
185 value: res,
186 index: i,
187 }
188 }
189
190 /// Recovers the constant term of a polynomial of degree less than `t` using at least `t` evaluations of the polynomial.
191 ///
192 /// This function uses Lagrange interpolation to compute the constant term (i.e., the value of the polynomial at `x=0`)
193 /// given at least `t` distinct evaluations of the polynomial. Each evaluation is assumed to have a unique index,
194 /// which is mapped to a unique x-value as `x = index + 1`.
195 ///
196 /// # Warning
197 ///
198 /// This function assumes that each evaluation has a unique index. If there are duplicate indices, the function may
199 /// fail with an error when attempting to compute the inverse of zero.
200 pub fn recover<'a, I>(t: u32, evals: I) -> Result<C, Error>
201 where
202 C: 'a,
203 I: IntoIterator<Item = &'a Eval<C>>,
204 {
205 // Reference: https://github.com/celo-org/celo-threshold-bls-rs/blob/a714310be76620e10e8797d6637df64011926430/crates/threshold-bls/src/poly.rs#L131-L165
206
207 // Check if we have at least `t` evaluations; if not, return an error
208 let t = t as usize;
209 let mut evals = evals.into_iter().collect::<Vec<_>>();
210 if evals.len() < t {
211 return Err(Error::NotEnoughPartialSignatures(t, evals.len()));
212 }
213
214 // Convert the first `t` sorted shares into scalars
215 //
216 // We sort the evaluations by index to ensure that two invocations of
217 // `recover` select the same evals.
218 evals.sort_by_key(|e| e.index);
219
220 // Take the first `t` evaluations and prepare them for interpolation
221 //
222 // Each index `i` is mapped to `x = i + 1` to avoid `x=0` (the constant term we’re recovering).
223 let xs = evals
224 .into_iter()
225 .take(t)
226 .fold(Vec::with_capacity(t), |mut m, sh| {
227 let mut xi = Scalar::zero();
228 xi.set_int(sh.index + 1);
229 m.push((sh.index, (xi, &sh.value)));
230 m
231 });
232
233 // Use Lagrange interpolation to compute the constant term at `x=0`
234 //
235 // The constant term is `sum_{i=1 to t} yi * l_i(0)`, where `l_i(0) = product_{j != i} (xj / (xj - xi))`.
236 xs.iter().try_fold(C::zero(), |mut acc, (i, (xi, yi))| {
237 let (mut num, den) = xs.iter().fold(
238 (Scalar::one(), Scalar::one()),
239 |(mut num, mut den), (j, (xj, _))| {
240 if i != j {
241 // Include `xj` in the numerator product for `l_i(0)`
242 num.mul(xj);
243
244 // Compute `xj - xi` and include it in the denominator product
245 let mut tmp = xj.clone();
246 tmp.sub(xi);
247 den.mul(&tmp);
248 }
249 (num, den)
250 },
251 );
252
253 // Compute the inverse of the denominator product; fails if den is zero (e.g., duplicate `xj`)
254 let inv = den.inverse().ok_or(Error::NoInverse)?;
255
256 // Compute `l_i(0) = num * inv`, the Lagrange basis coefficient at `x=0`
257 num.mul(&inv);
258
259 // Scale `yi` by `l_i(0)` to contribute to the constant term
260 let mut yi_scaled = (*yi).clone();
261 yi_scaled.mul(&num);
262
263 // Add `yi * l_i(0)` to the running sum
264 acc.add(&yi_scaled);
265 Ok(acc)
266 })
267 }
268}
269
270impl<C: Element> Write for Poly<C> {
271 fn write(&self, buf: &mut impl BufMut) {
272 for c in &self.0 {
273 c.write(buf);
274 }
275 }
276}
277
278impl<C: Element> Read<usize> for Poly<C> {
279 fn read_cfg(buf: &mut impl Buf, expected: &usize) -> Result<Self, CodecError> {
280 let expected_size = C::SIZE * (*expected);
281 if buf.remaining() < expected_size {
282 return Err(CodecError::EndOfBuffer);
283 }
284 let mut coeffs = Vec::with_capacity(*expected);
285 for _ in 0..*expected {
286 coeffs.push(C::read(buf)?);
287 }
288 Ok(Self(coeffs))
289 }
290}
291
292impl<C: Element> EncodeSize for Poly<C> {
293 fn encode_size(&self) -> usize {
294 C::SIZE * self.0.len()
295 }
296}
297
298/// Returns the public key of the polynomial (constant term).
299pub fn public(public: &Public) -> &group::Public {
300 public.constant()
301}
302
303#[cfg(test)]
304pub mod tests {
305 use commonware_codec::{Decode, Encode};
306
307 // Reference: https://github.com/celo-org/celo-threshold-bls-rs/blob/b0ef82ff79769d085a5a7d3f4fe690b1c8fe6dc9/crates/threshold-bls/src/poly.rs#L355-L604
308 use super::*;
309 use crate::bls12381::primitives::group::{Scalar, G2};
310
311 #[test]
312 fn poly_degree() {
313 let s = 5;
314 let p = new(s);
315 assert_eq!(p.degree(), s);
316 }
317
318 #[test]
319 fn add_zero() {
320 let p1 = new(3);
321 let p2 = Poly::<Scalar>::zero();
322 let mut res = p1.clone();
323 res.add(&p2);
324 assert_eq!(res, p1);
325
326 let p1 = Poly::<Scalar>::zero();
327 let p2 = new(3);
328 let mut res = p1;
329 res.add(&p2);
330 assert_eq!(res, p2);
331 }
332
333 #[test]
334 fn interpolation_insufficient_shares() {
335 let degree = 4;
336 let threshold = degree + 1;
337 let poly = new(degree);
338 let shares = (0..threshold - 1)
339 .map(|i| poly.evaluate(i))
340 .collect::<Vec<_>>();
341 Poly::recover(threshold, &shares).unwrap_err();
342 }
343
344 #[test]
345 fn commit() {
346 let secret = new(5);
347 let coeffs = secret.0.clone();
348 let commitment = coeffs
349 .iter()
350 .map(|coeff| {
351 let mut p = G2::one();
352 p.mul(coeff);
353 p
354 })
355 .collect::<Vec<_>>();
356 let commitment = Poly::from(commitment);
357 assert_eq!(commitment, Poly::commit(secret));
358 }
359
360 fn pow(base: Scalar, pow: usize) -> Scalar {
361 let mut res = Scalar::one();
362 for _ in 0..pow {
363 res.mul(&base)
364 }
365 res
366 }
367
368 #[test]
369 fn addition() {
370 for deg1 in 0..100u32 {
371 for deg2 in 0..100u32 {
372 let p1 = new(deg1);
373 let p2 = new(deg2);
374 let mut res = p1.clone();
375 res.add(&p2);
376
377 let (larger, smaller) = if p1.degree() > p2.degree() {
378 (&p1, &p2)
379 } else {
380 (&p2, &p1)
381 };
382
383 for i in 0..larger.degree() + 1 {
384 let i = i as usize;
385 if i < (smaller.degree() + 1) as usize {
386 let mut coeff_sum = p1.0[i].clone();
387 coeff_sum.add(&p2.0[i]);
388 assert_eq!(res.0[i], coeff_sum);
389 } else {
390 assert_eq!(res.0[i], larger.0[i]);
391 }
392 }
393 assert_eq!(
394 res.degree(),
395 larger.degree(),
396 "deg1={}, deg2={}",
397 deg1,
398 deg2
399 );
400 }
401 }
402 }
403
404 #[test]
405 fn interpolation() {
406 for degree in 0..100u32 {
407 for num_evals in 0..100u32 {
408 let poly = new(degree);
409 let expected = poly.0[0].clone();
410
411 let shares = (0..num_evals).map(|i| poly.evaluate(i)).collect::<Vec<_>>();
412 let recovered_constant = Poly::recover(num_evals, &shares).unwrap();
413
414 if num_evals > degree {
415 assert_eq!(
416 expected, recovered_constant,
417 "degree={}, num_evals={}",
418 degree, num_evals
419 );
420 } else {
421 assert_ne!(
422 expected, recovered_constant,
423 "degree={}, num_evals={}",
424 degree, num_evals
425 );
426 }
427 }
428 }
429 }
430
431 #[test]
432 fn evaluate() {
433 for d in 0..100u32 {
434 for idx in 0..100_u32 {
435 let mut x = Scalar::zero();
436 x.set_int(idx + 1);
437
438 let p1 = new(d);
439 let evaluation = p1.evaluate(idx).value;
440
441 let coeffs = p1.0;
442 let mut sum = coeffs[0].clone();
443 for (i, coeff) in coeffs
444 .into_iter()
445 .enumerate()
446 .take((d + 1) as usize)
447 .skip(1)
448 {
449 let xi = pow(x.clone(), i);
450 let mut var = coeff;
451 var.mul(&xi);
452 sum.add(&var);
453 }
454
455 assert_eq!(sum, evaluation, "degree={}, idx={}", d, idx);
456 }
457 }
458 }
459
460 #[test]
461 fn test_codec() {
462 let original = new(5);
463 let encoded = original.encode();
464 let decoded = Poly::<Scalar>::decode_cfg(encoded, &(original.required() as usize)).unwrap();
465 assert_eq!(original, decoded);
466 }
467}