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}