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