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}