blsttc/
poly.rs

1//! Utilities for distributed key generation: uni- and bivariate polynomials and commitments.
2//!
3//! If `G` is a group of prime order `r` (written additively), and `g` is a generator, then
4//! multiplication by integers factors through `r`, so the map `x -> x * g` (the sum of `x`
5//! copies of `g`) is a homomorphism from the field `Fr` of integers modulo `r` to `G`. If the
6//! _discrete logarithm_ is hard, i.e. it is infeasible to reverse this map, then `x * g` can be
7//! considered a _commitment_ to `x`: By publishing it, you can guarantee to others that you won't
8//! change your mind about the value `x`, without revealing it.
9//!
10//! This concept extends to polynomials: If you have a polynomial `f` over `Fr`, defined as
11//! `a * X * X + b * X + c`, you can publish `a * g`, `b * g` and `c * g`. Then others will be able
12//! to verify any single value `f(x)` of the polynomial without learning the original polynomial,
13//! because `f(x) * g == x * x * (a * g) + x * (b * g) + (c * g)`. Only after learning three (in
14//! general `degree + 1`) values, they can interpolate `f` itself.
15//!
16//! This module defines univariate polynomials (in one variable) and _symmetric_ bivariate
17//! polynomials (in two variables) over a field `Fr`, as well as their _commitments_ in `G`.
18
19use std::borrow::Borrow;
20use std::cmp::Ordering;
21use std::fmt::{self, Debug, Formatter};
22use std::hash::{Hash, Hasher};
23use std::iter::repeat_with;
24use std::ops::{AddAssign, Mul, MulAssign, SubAssign};
25use std::{cmp, iter, ops};
26
27use group::{prime::PrimeCurveAffine, Curve, Group};
28use rand::Rng;
29use serde::{Deserialize, Serialize};
30use zeroize::Zeroize;
31
32use crate::cmp_pairing::cmp_affine;
33use crate::convert::{fr_from_bytes, g1_from_bytes};
34use crate::error::{Error, Result};
35use crate::into_fr::IntoFr;
36use crate::secret::clear_fr;
37use crate::{Field, Fr, G1Affine, G1Projective, PK_SIZE, SK_SIZE};
38
39/// A univariate polynomial in the prime field.
40#[derive(Serialize, Deserialize, PartialEq, Eq, Clone)]
41pub struct Poly {
42    /// The coefficients of a polynomial.
43    #[serde(with = "super::serde_impl::field_vec")]
44    pub(super) coeff: Vec<Fr>,
45}
46
47impl Zeroize for Poly {
48    fn zeroize(&mut self) {
49        for fr in self.coeff.iter_mut() {
50            clear_fr(fr)
51        }
52    }
53}
54
55impl Drop for Poly {
56    fn drop(&mut self) {
57        self.zeroize();
58    }
59}
60
61/// A debug statement where the `coeff` vector of prime field elements has been redacted.
62impl Debug for Poly {
63    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
64        f.debug_struct("Poly").field("coeff", &"...").finish()
65    }
66}
67
68#[allow(clippy::suspicious_op_assign_impl)]
69impl<B: Borrow<Poly>> ops::AddAssign<B> for Poly {
70    fn add_assign(&mut self, rhs: B) {
71        let len = self.coeff.len();
72        let rhs_len = rhs.borrow().coeff.len();
73        if rhs_len > len {
74            self.coeff.resize(rhs_len, Fr::zero());
75        }
76        for (self_c, rhs_c) in self.coeff.iter_mut().zip(&rhs.borrow().coeff) {
77            self_c.add_assign(rhs_c);
78        }
79        self.remove_zeros();
80    }
81}
82
83impl<'a, B: Borrow<Poly>> ops::Add<B> for &'a Poly {
84    type Output = Poly;
85
86    fn add(self, rhs: B) -> Poly {
87        (*self).clone() + rhs
88    }
89}
90
91impl<B: Borrow<Poly>> ops::Add<B> for Poly {
92    type Output = Poly;
93
94    fn add(mut self, rhs: B) -> Poly {
95        self += rhs;
96        self
97    }
98}
99
100impl ops::Add<Fr> for Poly {
101    type Output = Poly;
102
103    fn add(mut self, rhs: Fr) -> Self::Output {
104        if self.is_zero() && !bool::from(rhs.is_zero()) {
105            self.coeff.push(rhs);
106        } else {
107            self.coeff[0].add_assign(&rhs);
108            self.remove_zeros();
109        }
110        self
111    }
112}
113
114impl ops::Add<u64> for Poly {
115    type Output = Poly;
116
117    fn add(self, rhs: u64) -> Self::Output {
118        self + Fr::from(rhs)
119    }
120}
121
122impl<B: Borrow<Poly>> ops::SubAssign<B> for Poly {
123    fn sub_assign(&mut self, rhs: B) {
124        let len = self.coeff.len();
125        let rhs_len = rhs.borrow().coeff.len();
126        if rhs_len > len {
127            self.coeff.resize(rhs_len, Fr::zero());
128        }
129        for (self_c, rhs_c) in self.coeff.iter_mut().zip(&rhs.borrow().coeff) {
130            self_c.sub_assign(rhs_c);
131        }
132        self.remove_zeros();
133    }
134}
135
136impl<'a, B: Borrow<Poly>> ops::Sub<B> for &'a Poly {
137    type Output = Poly;
138
139    fn sub(self, rhs: B) -> Poly {
140        (*self).clone() - rhs
141    }
142}
143
144impl<B: Borrow<Poly>> ops::Sub<B> for Poly {
145    type Output = Poly;
146
147    fn sub(mut self, rhs: B) -> Poly {
148        self -= rhs;
149        self
150    }
151}
152
153impl ops::Sub<Fr> for Poly {
154    type Output = Poly;
155
156    fn sub(self, mut rhs: Fr) -> Self::Output {
157        rhs = -rhs;
158        self + rhs
159    }
160}
161
162impl ops::Sub<u64> for Poly {
163    type Output = Poly;
164
165    fn sub(self, rhs: u64) -> Self::Output {
166        self - Fr::from(rhs)
167    }
168}
169
170// Clippy thinks using any `+` and `-` in a `Mul` implementation is suspicious.
171#[allow(clippy::suspicious_arithmetic_impl)]
172impl<'a, B: Borrow<Poly>> ops::Mul<B> for &'a Poly {
173    type Output = Poly;
174
175    fn mul(self, rhs: B) -> Self::Output {
176        let rhs = rhs.borrow();
177        if rhs.is_zero() || self.is_zero() {
178            return Poly::zero();
179        }
180        let n_coeffs = self.coeff.len() + rhs.coeff.len() - 1;
181        let mut coeffs = vec![Fr::zero(); n_coeffs];
182        let mut tmp = Fr::zero();
183        for (i, ca) in self.coeff.iter().enumerate() {
184            for (j, cb) in rhs.coeff.iter().enumerate() {
185                tmp = *ca;
186                tmp.mul_assign(cb);
187                coeffs[i + j].add_assign(&tmp);
188            }
189        }
190        clear_fr(&mut tmp);
191        Poly::from(coeffs)
192    }
193}
194
195impl<B: Borrow<Poly>> ops::Mul<B> for Poly {
196    type Output = Poly;
197
198    fn mul(self, rhs: B) -> Self::Output {
199        &self * rhs
200    }
201}
202
203impl<B: Borrow<Self>> ops::MulAssign<B> for Poly {
204    fn mul_assign(&mut self, rhs: B) {
205        *self = &*self * rhs;
206    }
207}
208
209impl ops::MulAssign<Fr> for Poly {
210    fn mul_assign(&mut self, rhs: Fr) {
211        if bool::from(rhs.is_zero()) {
212            self.zeroize();
213            self.coeff.clear();
214        } else {
215            for c in &mut self.coeff {
216                c.mul_assign(&rhs);
217            }
218        }
219    }
220}
221
222impl<'a> ops::Mul<&'a Fr> for Poly {
223    type Output = Poly;
224
225    fn mul(mut self, rhs: &Fr) -> Self::Output {
226        if bool::from(rhs.is_zero()) {
227            self.zeroize();
228            self.coeff.clear();
229        } else {
230            self.coeff.iter_mut().for_each(|c| c.mul_assign(rhs));
231        }
232        self
233    }
234}
235
236impl ops::Mul<Fr> for Poly {
237    type Output = Poly;
238
239    fn mul(self, rhs: Fr) -> Self::Output {
240        let rhs = &rhs;
241        self * rhs
242    }
243}
244
245impl<'a> ops::Mul<&'a Fr> for &'a Poly {
246    type Output = Poly;
247
248    fn mul(self, rhs: &Fr) -> Self::Output {
249        (*self).clone() * rhs
250    }
251}
252
253impl<'a> ops::Mul<Fr> for &'a Poly {
254    type Output = Poly;
255
256    fn mul(self, rhs: Fr) -> Self::Output {
257        (*self).clone() * rhs
258    }
259}
260
261impl ops::Mul<u64> for Poly {
262    type Output = Poly;
263
264    fn mul(self, rhs: u64) -> Self::Output {
265        self * Fr::from(rhs)
266    }
267}
268
269/// Creates a new `Poly` instance from a vector of prime field elements representing the
270/// coefficients of the polynomial.
271impl From<Vec<Fr>> for Poly {
272    fn from(coeff: Vec<Fr>) -> Self {
273        Poly { coeff }
274    }
275}
276
277impl Poly {
278    /// Creates a random polynomial.
279    ///
280    /// # Panics
281    ///
282    /// Panics if the `degree` is too large for the coefficients to fit into a `Vec`.
283    pub fn random<R: Rng>(degree: usize, rng: &mut R) -> Self {
284        Poly::try_random(degree, rng)
285            .unwrap_or_else(|e| panic!("Failed to create random `Poly`: {e}"))
286    }
287
288    /// Creates a random polynomial. This constructor is identical to the `Poly::random()`
289    /// constructor in every way except that this constructor will return an `Err` where
290    /// `try_random` would return an error.
291    pub fn try_random<R: Rng>(degree: usize, rng: &mut R) -> Result<Self> {
292        if degree == usize::max_value() {
293            return Err(Error::DegreeTooHigh);
294        }
295        let coeff: Vec<Fr> = repeat_with(|| Fr::random(&mut *rng))
296            .take(degree + 1)
297            .collect();
298        Ok(Poly::from(coeff))
299    }
300
301    /// Returns the polynomial with constant value `0`.
302    pub fn zero() -> Self {
303        Poly { coeff: vec![] }
304    }
305
306    /// Returns `true` if the polynomial is the constant value `0`.
307    pub fn is_zero(&self) -> bool {
308        self.coeff.iter().all(|coeff| bool::from(coeff.is_zero()))
309    }
310
311    /// Returns the polynomial with constant value `1`.
312    pub fn one() -> Self {
313        Poly::constant(Fr::one())
314    }
315
316    /// Returns the polynomial with constant value `c`.
317    pub fn constant(mut c: Fr) -> Self {
318        // We create a raw pointer to the field element within this method's stack frame so we can
319        // overwrite that portion of memory with zeros once we have copied the element onto the
320        // heap as part of the vector of polynomial coefficients.
321        let poly = Poly::from(vec![c]);
322        clear_fr(&mut c);
323        poly
324    }
325
326    /// Returns the identity function, i.e. the polynomial "`x`".
327    pub fn identity() -> Self {
328        Poly::monomial(1)
329    }
330
331    /// Returns the (monic) monomial: `x.pow(degree)`.
332    pub fn monomial(degree: usize) -> Self {
333        let coeff: Vec<Fr> = iter::repeat(Fr::zero())
334            .take(degree)
335            .chain(iter::once(Fr::one()))
336            .collect();
337        Poly::from(coeff)
338    }
339
340    /// Returns the unique polynomial `f` of degree `samples.len() - 1` with the given values
341    /// `(x, f(x))`.
342    pub fn interpolate<T, U, I>(samples_repr: I) -> Result<Self>
343    where
344        I: IntoIterator<Item = (T, U)>,
345        T: IntoFr,
346        U: IntoFr,
347    {
348        let convert = |(x, y): (T, U)| (x.into_fr(), y.into_fr());
349        let samples: Vec<(Fr, Fr)> = samples_repr.into_iter().map(convert).collect();
350        Poly::compute_interpolation(&samples)
351    }
352
353    /// Returns the degree.
354    pub fn degree(&self) -> usize {
355        self.coeff.len().saturating_sub(1)
356    }
357
358    /// Returns the value at the point `i`.
359    pub fn evaluate<T: IntoFr>(&self, i: T) -> Fr {
360        let mut result = match self.coeff.last() {
361            None => return Fr::zero(),
362            Some(c) => *c,
363        };
364        let x = i.into_fr();
365        for c in self.coeff.iter().rev().skip(1) {
366            result.mul_assign(&x);
367            result.add_assign(c);
368        }
369        result
370    }
371
372    /// Returns the corresponding commitment.
373    pub fn commitment(&self) -> Commitment {
374        let to_g1 = |c: &Fr| G1Affine::generator().mul(c).to_affine();
375        Commitment {
376            coeff: self.coeff.iter().map(to_g1).collect(),
377        }
378    }
379
380    /// Serializes to big endian bytes
381    pub fn to_bytes(&self) -> Vec<u8> {
382        let coeff_size = self.coeff.len();
383        let bytes_size = coeff_size * SK_SIZE;
384        let mut poly_bytes = vec![0; bytes_size];
385        for i in 0..coeff_size {
386            let fr = self.coeff[i];
387            let fr_bytes = fr.to_bytes_be();
388            let fr_size = fr_bytes.len();
389            for j in 0..fr_size {
390                poly_bytes[i * SK_SIZE + j] = fr_bytes[j];
391            }
392        }
393        poly_bytes
394    }
395
396    /// Deserializes from big endian bytes
397    pub fn from_bytes(bytes: Vec<u8>) -> Result<Self> {
398        let mut c: Vec<Fr> = vec![];
399        let coeff_size = bytes.len() / SK_SIZE;
400        for i in 0..coeff_size {
401            let mut fr_bytes = [0u8; SK_SIZE];
402            for j in 0..SK_SIZE {
403                fr_bytes[j] = bytes[i * SK_SIZE + j];
404            }
405            let fr = fr_from_bytes(fr_bytes)?;
406            c.push(fr);
407        }
408        Ok(Poly { coeff: c })
409    }
410
411    /// Removes all trailing zero coefficients.
412    fn remove_zeros(&mut self) {
413        let zeros = self
414            .coeff
415            .iter()
416            .rev()
417            .take_while(|c| bool::from(c.is_zero()))
418            .count();
419        let len = self.coeff.len() - zeros;
420        self.coeff.truncate(len);
421    }
422
423    /// Returns the unique polynomial `f` of degree `samples.len() - 1` with the given values
424    /// `(x, f(x))`.
425    fn compute_interpolation(samples: &[(Fr, Fr)]) -> Result<Self> {
426        if samples.is_empty() {
427            return Ok(Poly::zero());
428        }
429        // Interpolates on the first `i` samples.
430        let mut poly = Poly::constant(samples[0].1);
431        let mut minus_s0 = samples[0].0;
432        minus_s0 = -minus_s0;
433        // Is zero on the first `i` samples.
434        let mut base = Poly::from(vec![minus_s0, Fr::one()]);
435
436        // We update `base` so that it is always zero on all previous samples, and `poly` so that
437        // it has the correct values on the previous samples.
438        for (ref x, ref y) in &samples[1..] {
439            // Scale `base` so that its value at `x` is the difference between `y` and `poly`'s
440            // current value at `x`: Adding it to `poly` will then make it correct for `x`.
441            let mut diff = *y;
442            diff.sub_assign(&poly.evaluate(x));
443            let base_val = base.evaluate(x);
444            //Can we avoid using unwrap with CtOption? Should always be ok
445            //because of `is_none` check.
446            let base_val_inv = &base_val.invert();
447            if base_val_inv.is_none().into() {
448                return Err(Error::DuplicateEntry);
449            }
450            diff.mul_assign(&base_val_inv.unwrap());
451            base *= diff;
452            poly += &base;
453
454            // Finally, multiply `base` by X - x, so that it is zero at `x`, too, now.
455            base *= Poly::from(vec![-x, Fr::one()]);
456        }
457        Ok(poly)
458    }
459
460    /// Generates a non-redacted debug string. This method differs from
461    /// the `Debug` implementation in that it *does* leak the secret prime
462    /// field elements.
463    pub fn reveal(&self) -> String {
464        format!("Poly {{ coeff: {:?} }}", self.coeff)
465    }
466}
467
468/// A commitment to a univariate polynomial.
469#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
470pub struct Commitment {
471    /// The coefficients of the polynomial.
472    #[serde(with = "super::serde_impl::affine_vec")]
473    pub(super) coeff: Vec<G1Affine>,
474}
475
476/// Creates a new `Commitment` instance
477impl From<Vec<G1Affine>> for Commitment {
478    fn from(coeff: Vec<G1Affine>) -> Self {
479        Commitment { coeff }
480    }
481}
482
483impl PartialOrd for Commitment {
484    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
485        Some(self.cmp(other))
486    }
487}
488
489impl Ord for Commitment {
490    fn cmp(&self, other: &Self) -> Ordering {
491        self.coeff.len().cmp(&other.coeff.len()).then_with(|| {
492            self.coeff
493                .iter()
494                .zip(&other.coeff)
495                .find(|(x, y)| x != y)
496                .map_or(Ordering::Equal, |(x, y)| cmp_affine(x, y))
497        })
498    }
499}
500
501impl Hash for Commitment {
502    fn hash<H: Hasher>(&self, state: &mut H) {
503        self.coeff.len().hash(state);
504        for c in &self.coeff {
505            c.to_compressed().hash(state);
506        }
507    }
508}
509
510impl<B: Borrow<Commitment>> ops::AddAssign<B> for Commitment {
511    fn add_assign(&mut self, rhs: B) {
512        let len = cmp::max(self.coeff.len(), rhs.borrow().coeff.len());
513        self.coeff.resize(len, G1Affine::identity());
514        for (self_c, rhs_c) in self.coeff.iter_mut().zip(&rhs.borrow().coeff) {
515            *self_c = (*self_c + G1Projective::from(rhs_c)).to_affine();
516        }
517        self.remove_zeros();
518    }
519}
520
521impl<'a, B: Borrow<Commitment>> ops::Add<B> for &'a Commitment {
522    type Output = Commitment;
523
524    fn add(self, rhs: B) -> Commitment {
525        (*self).clone() + rhs
526    }
527}
528
529impl<B: Borrow<Commitment>> ops::Add<B> for Commitment {
530    type Output = Commitment;
531
532    fn add(mut self, rhs: B) -> Commitment {
533        self += rhs;
534        self
535    }
536}
537
538impl Commitment {
539    /// Returns the polynomial's degree.
540    pub fn degree(&self) -> usize {
541        self.coeff.len() - 1
542    }
543
544    /// Returns the `i`-th public key share.
545    pub fn evaluate<T: IntoFr>(&self, i: T) -> G1Affine {
546        let mut result = match self.coeff.last() {
547            None => return G1Affine::identity(),
548            Some(c) => G1Projective::from(c),
549        };
550        let x = i.into_fr();
551        for c in self.coeff.iter().rev().skip(1) {
552            result.mul_assign(x);
553            result.add_assign(c);
554        }
555        result.to_affine()
556    }
557
558    /// Serializes to big endian bytes
559    pub fn to_bytes(&self) -> Vec<u8> {
560        let coeff_size = self.coeff.len();
561        let bytes_size = coeff_size * PK_SIZE;
562        let mut commit_bytes = vec![0; bytes_size];
563        for i in 0..coeff_size {
564            let g1 = self.coeff[i];
565            let g1_bytes = g1.to_compressed();
566            let g1_size = g1_bytes.len();
567            for j in 0..g1_size {
568                commit_bytes[i * PK_SIZE + j] = g1_bytes[j];
569            }
570        }
571        commit_bytes
572    }
573
574    /// Deserializes from big endian bytes
575    pub fn from_bytes(bytes: Vec<u8>) -> Result<Self> {
576        let mut c: Vec<G1Affine> = vec![];
577        let coeff_size = bytes.len() / PK_SIZE;
578        for i in 0..coeff_size {
579            let mut g1_bytes = [0; PK_SIZE];
580            for j in 0..PK_SIZE {
581                g1_bytes[j] = bytes[i * PK_SIZE + j];
582            }
583            let g1 = g1_from_bytes(g1_bytes)?;
584            c.push(g1);
585        }
586        Ok(Commitment { coeff: c })
587    }
588
589    /// Removes all trailing zero coefficients.
590    fn remove_zeros(&mut self) {
591        let zeros = self
592            .coeff
593            .iter()
594            .rev()
595            .take_while(|c| bool::from(c.is_identity()))
596            .count();
597        let len = self.coeff.len() - zeros;
598        self.coeff.truncate(len)
599    }
600}
601
602/// A symmetric bivariate polynomial in the prime field.
603///
604/// This can be used for Verifiable Secret Sharing and Distributed Key Generation. See the module
605/// documentation for details.
606#[derive(Clone)]
607pub struct BivarPoly {
608    /// The polynomial's degree in each of the two variables.
609    degree: usize,
610    /// The coefficients of the polynomial. Coefficient `(i, j)` for `i <= j` is in position
611    /// `j * (j + 1) / 2 + i`.
612    coeff: Vec<Fr>,
613}
614
615impl Zeroize for BivarPoly {
616    fn zeroize(&mut self) {
617        for fr in self.coeff.iter_mut() {
618            clear_fr(fr)
619        }
620        self.degree.zeroize();
621    }
622}
623
624impl Drop for BivarPoly {
625    fn drop(&mut self) {
626        self.zeroize();
627    }
628}
629
630/// A debug statement where the `coeff` vector has been redacted.
631impl Debug for BivarPoly {
632    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
633        f.debug_struct("BivarPoly")
634            .field("degree", &self.degree)
635            .field("coeff", &"...")
636            .finish()
637    }
638}
639
640impl BivarPoly {
641    /// Creates a random polynomial.
642    ///
643    /// # Panics
644    ///
645    /// Panics if the degree is too high for the coefficients to fit into a `Vec`.
646    pub fn random<R: Rng>(degree: usize, rng: &mut R) -> Self {
647        BivarPoly::try_random(degree, rng).unwrap_or_else(|e| {
648            panic!("Failed to create random `BivarPoly` of degree {degree}: {e}")
649        })
650    }
651
652    /// Creates a random polynomial.
653    pub fn try_random<R: Rng>(degree: usize, rng: &mut R) -> Result<Self> {
654        let len = coeff_pos(degree, degree)
655            .and_then(|l| l.checked_add(1))
656            .ok_or(Error::DegreeTooHigh)?;
657        let poly = BivarPoly {
658            degree,
659            coeff: repeat_with(|| Fr::random(&mut *rng)).take(len).collect(),
660        };
661        Ok(poly)
662    }
663
664    /// Returns the polynomial's degree; which is the same in both variables.
665    pub fn degree(&self) -> usize {
666        self.degree
667    }
668
669    /// Returns the polynomial's value at the point `(x, y)`.
670    pub fn evaluate<T: IntoFr>(&self, x: T, y: T) -> Fr {
671        let x_pow = self.powers(x);
672        let y_pow = self.powers(y);
673        // TODO: Can we save a few multiplication steps here due to the symmetry?
674        let mut result = Fr::zero();
675        for (i, x_pow_i) in x_pow.into_iter().enumerate() {
676            for (j, y_pow_j) in y_pow.iter().enumerate() {
677                let index = coeff_pos(i, j).expect("polynomial degree too high");
678                let mut summand = self.coeff[index];
679                summand.mul_assign(&x_pow_i);
680                summand.mul_assign(y_pow_j);
681                result.add_assign(&summand);
682            }
683        }
684        result
685    }
686
687    /// Returns the `x`-th row, as a univariate polynomial.
688    pub fn row<T: IntoFr>(&self, x: T) -> Poly {
689        let x_pow = self.powers(x);
690        let coeff: Vec<Fr> = (0..=self.degree)
691            .map(|i| {
692                // TODO: clear these secrets from the stack.
693                let mut result = Fr::zero();
694                for (j, x_pow_j) in x_pow.iter().enumerate() {
695                    let index = coeff_pos(i, j).expect("polynomial degree too high");
696                    let mut summand = self.coeff[index];
697                    summand.mul_assign(x_pow_j);
698                    result.add_assign(&summand);
699                }
700                result
701            })
702            .collect();
703        Poly::from(coeff)
704    }
705
706    /// Returns the corresponding commitment. That information can be shared publicly.
707    pub fn commitment(&self) -> BivarCommitment {
708        let to_pub = |c: &Fr| G1Affine::generator().mul(c).to_affine();
709        BivarCommitment {
710            degree: self.degree,
711            coeff: self.coeff.iter().map(to_pub).collect(),
712        }
713    }
714
715    /// Returns the `0`-th to `degree`-th power of `x`.
716    fn powers<T: IntoFr>(&self, x: T) -> Vec<Fr> {
717        powers(x, self.degree)
718    }
719
720    /// Generates a non-redacted debug string. This method differs from the
721    /// `Debug` implementation in that it *does* leak the the struct's
722    /// internal state.
723    pub fn reveal(&self) -> String {
724        format!(
725            "BivarPoly {{ degree: {}, coeff: {:?} }}",
726            self.degree, self.coeff
727        )
728    }
729
730    /// Serializes to big endian bytes
731    pub fn to_bytes(&self) -> Vec<u8> {
732        let coeff_size = self.coeff.len();
733        let bytes_size = coeff_size * SK_SIZE;
734        let mut poly_bytes = vec![0; bytes_size];
735        for i in 0..coeff_size {
736            let fr = self.coeff[i];
737            let fr_bytes = fr.to_bytes_be();
738            let fr_size = fr_bytes.len();
739            for j in 0..fr_size {
740                poly_bytes[i * SK_SIZE + j] = fr_bytes[j];
741            }
742        }
743        poly_bytes
744    }
745
746    /// Deserializes from big endian bytes
747    pub fn from_bytes(bytes: Vec<u8>) -> Result<Self> {
748        let mut c: Vec<Fr> = vec![];
749        let coeff_size = bytes.len() / SK_SIZE;
750        for coeff_index in 0..coeff_size {
751            // get the Fr for this coeff
752            let mut fr_bytes = [0u8; SK_SIZE];
753            for i in 0..SK_SIZE {
754                fr_bytes[i] = bytes[coeff_index * SK_SIZE + i];
755            }
756            let fr = fr_from_bytes(fr_bytes)?;
757            c.push(fr);
758        }
759        let d = ((2 * coeff_size) as f64).sqrt() as usize - 1;
760        Ok(BivarPoly {
761            degree: d,
762            coeff: c,
763        })
764    }
765}
766
767/// A commitment to a symmetric bivariate polynomial.
768#[derive(Debug, Clone, Eq, PartialEq)]
769pub struct BivarCommitment {
770    /// The polynomial's degree in each of the two variables.
771    pub(crate) degree: usize,
772    /// The commitments to the coefficients.
773    pub(crate) coeff: Vec<G1Affine>,
774}
775
776impl Hash for BivarCommitment {
777    fn hash<H: Hasher>(&self, state: &mut H) {
778        self.degree.hash(state);
779        for c in &self.coeff {
780            c.to_compressed().hash(state);
781        }
782    }
783}
784
785impl PartialOrd for BivarCommitment {
786    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
787        Some(self.cmp(other))
788    }
789}
790
791impl Ord for BivarCommitment {
792    fn cmp(&self, other: &Self) -> Ordering {
793        self.degree.cmp(&other.degree).then_with(|| {
794            self.coeff
795                .iter()
796                .zip(&other.coeff)
797                .find(|(x, y)| x != y)
798                .map_or(Ordering::Equal, |(x, y)| cmp_affine(x, y))
799        })
800    }
801}
802
803impl BivarCommitment {
804    /// Returns the polynomial's degree: It is the same in both variables.
805    pub fn degree(&self) -> usize {
806        self.degree
807    }
808
809    /// Returns the commitment's value at the point `(x, y)`.
810    pub fn evaluate<T: IntoFr>(&self, x: T, y: T) -> G1Affine {
811        let x_pow = self.powers(x);
812        let y_pow = self.powers(y);
813        // TODO: Can we save a few multiplication steps here due to the symmetry?
814        let mut result = G1Projective::identity();
815        for (i, x_pow_i) in x_pow.into_iter().enumerate() {
816            for (j, y_pow_j) in y_pow.iter().enumerate() {
817                let index = coeff_pos(i, j).expect("polynomial degree too high");
818                let mut summand = self.coeff[index];
819                summand.mul_assign(x_pow_i);
820                summand.mul_assign(*y_pow_j);
821                result.add_assign(&summand);
822            }
823        }
824        result.to_affine()
825    }
826
827    /// Returns the `x`-th row, as a commitment to a univariate polynomial.
828    pub fn row<T: IntoFr>(&self, x: T) -> Commitment {
829        let x_pow = self.powers(x);
830        let coeff: Vec<G1Affine> = (0..=self.degree)
831            .map(|i| {
832                let mut result = G1Projective::identity();
833                for (j, x_pow_j) in x_pow.iter().enumerate() {
834                    let index = coeff_pos(i, j).expect("polynomial degree too high");
835                    let mut summand = self.coeff[index];
836                    summand.mul_assign(*x_pow_j);
837                    result.add_assign(&summand);
838                }
839                result.to_affine()
840            })
841            .collect();
842        Commitment { coeff }
843    }
844
845    /// Returns the `0`-th to `degree`-th power of `x`.
846    fn powers<T: IntoFr>(&self, x: T) -> Vec<Fr> {
847        powers(x, self.degree)
848    }
849
850    /// Serializes to big endian bytes
851    pub fn to_bytes(&self) -> Vec<u8> {
852        let coeff_size = self.coeff.len();
853        let bytes_size = coeff_size * PK_SIZE;
854        let mut commit_bytes = vec![0; bytes_size];
855        for i in 0..coeff_size {
856            let g1 = self.coeff[i];
857            let g1_bytes = g1.to_compressed();
858            let g1_size = g1_bytes.len();
859            // TODO if not equal then should do padding instead of fail?
860            assert_eq!(g1_size, PK_SIZE);
861            for j in 0..g1_size {
862                commit_bytes[i * PK_SIZE + j] = g1_bytes[j];
863            }
864        }
865        commit_bytes
866    }
867
868    /// Deserializes from big endian bytes
869    pub fn from_bytes(bytes: Vec<u8>) -> Result<Self> {
870        let mut c: Vec<G1Affine> = vec![];
871        let coeff_size = bytes.len() / PK_SIZE;
872        for i in 0..coeff_size {
873            let mut g1_bytes = [0; PK_SIZE];
874            for j in 0..PK_SIZE {
875                g1_bytes[j] = bytes[i * PK_SIZE + j];
876            }
877            let g1 = g1_from_bytes(g1_bytes)?;
878            c.push(g1);
879        }
880        let d = ((2 * coeff_size) as f64).sqrt() as usize - 1;
881        Ok(BivarCommitment {
882            degree: d,
883            coeff: c,
884        })
885    }
886}
887
888/// Returns the `0`-th to `degree`-th power of `x`.
889fn powers<T: IntoFr>(into_x: T, degree: usize) -> Vec<Fr> {
890    let x = into_x.into_fr();
891    let mut x_pow_i = Fr::one();
892    iter::once(x_pow_i)
893        .chain((0..degree).map(|_| {
894            x_pow_i.mul_assign(&x);
895            x_pow_i
896        }))
897        .collect()
898}
899
900/// Returns the position of coefficient `(i, j)` in the vector describing a symmetric bivariate
901/// polynomial. If `i` or `j` are too large to represent the position as a `usize`, `None` is
902/// returned.
903pub(crate) fn coeff_pos(i: usize, j: usize) -> Option<usize> {
904    // Since the polynomial is symmetric, we can order such that `j >= i`.
905    let (j, i) = if j >= i { (j, i) } else { (i, j) };
906    i.checked_add(j.checked_mul(j.checked_add(1)?)? / 2)
907}
908
909#[cfg(test)]
910mod tests {
911
912    use super::*;
913    use eyre::{eyre, Result};
914    use group::{ff::Field, prime::PrimeCurveAffine, Curve};
915    use hex_fmt::HexFmt;
916    use std::collections::BTreeMap;
917    use std::ops::{AddAssign, Mul};
918
919    #[test]
920    fn test_coeff_pos() {
921        let mut i = 0;
922        let mut j = 0;
923        for n in 0..100 {
924            assert_eq!(Some(n), coeff_pos(i, j));
925            if i >= j {
926                j += 1;
927                i = 0;
928            } else {
929                i += 1;
930            }
931        }
932        let too_large = 1 << (0usize.count_zeros() / 2);
933        assert_eq!(None, coeff_pos(0, too_large));
934    }
935
936    #[test]
937    fn poly() -> Result<()> {
938        // The polynomial 5 X³ + X - 2.
939        let x_pow_3 = Poly::monomial(3);
940        let x_pow_1 = Poly::monomial(1);
941        let poly = x_pow_3 * 5 + x_pow_1 - 2;
942
943        let coeff: Vec<_> = [-2, 1, 0, 5].iter().map(IntoFr::into_fr).collect();
944        assert_eq!(Poly { coeff }, poly);
945        let samples = vec![(-1, -8), (2, 40), (3, 136), (5, 628)];
946        for &(x, y) in &samples {
947            assert_eq!(y.into_fr(), poly.evaluate(x));
948        }
949        let interp = Poly::interpolate(samples)?;
950        assert_eq!(interp, poly);
951        Ok(())
952    }
953
954    #[test]
955    fn test_zeroize() {
956        let mut poly = Poly::monomial(3) + Poly::monomial(2) - 1;
957        poly.zeroize();
958        assert!(poly.is_zero());
959        let mut bi_poly = BivarPoly::random(3, &mut rand::thread_rng());
960        let random_commitment = bi_poly.commitment();
961        bi_poly.zeroize();
962        let zero_commitment = bi_poly.commitment();
963        assert_ne!(random_commitment, zero_commitment);
964        let mut rng = rand::thread_rng();
965        let (x, y): (Fr, Fr) = (Fr::random(&mut rng), Fr::random(&mut rng));
966        assert_eq!(zero_commitment.evaluate(x, y), G1Affine::identity());
967    }
968
969    #[test]
970    fn distributed_key_generation() -> Result<()> {
971        let mut rng = rand::thread_rng();
972        let dealer_num = 3;
973        let node_num = 5;
974        let faulty_num = 2;
975
976        // For distributed key generation, a number of dealers, only one of who needs to be honest,
977        // generates random bivariate polynomials and publicly commits to them. In practice, the
978        // dealers can e.g. be any `faulty_num + 1` nodes.
979        let bi_polys: Vec<BivarPoly> = (0..dealer_num)
980            .map(|_| BivarPoly::random(faulty_num, &mut rng))
981            .collect();
982        let pub_bi_commits: Vec<_> = bi_polys.iter().map(BivarPoly::commitment).collect();
983
984        let mut sec_keys = vec![Fr::zero(); node_num];
985
986        // Each dealer sends row `m` to node `m`, where the index starts at `1`. Don't send row `0`
987        // to anyone! The nodes verify their rows, and send _value_ `s` on to node `s`. They again
988        // verify the values they received, and collect them.
989        for (bi_poly, bi_commit) in bi_polys.iter().zip(&pub_bi_commits) {
990            for m in 1..=node_num {
991                // Node `m` receives its row and verifies it.
992                let row_poly = bi_poly.row(m);
993                let row_commit = bi_commit.row(m);
994                assert_eq!(row_poly.commitment(), row_commit);
995                // Node `s` receives the `s`-th value and verifies it.
996                for s in 1..=node_num {
997                    let val = row_poly.evaluate(s);
998                    let val_g1 = G1Affine::generator().mul(val);
999                    assert_eq!(bi_commit.evaluate(m, s), val_g1.to_affine());
1000                    // The node can't verify this directly, but it should have the correct value:
1001                    assert_eq!(bi_poly.evaluate(m, s), val);
1002                }
1003
1004                // A cheating dealer who modified the polynomial would be detected.
1005                let x_pow_2 = Poly::monomial(2);
1006                let five = Poly::constant(5.into_fr());
1007                let wrong_poly = row_poly.clone() + x_pow_2 * five;
1008                assert_ne!(wrong_poly.commitment(), row_commit);
1009
1010                // If `2 * faulty_num + 1` nodes confirm that they received a valid row, then at
1011                // least `faulty_num + 1` honest ones did, and sent the correct values on to node
1012                // `s`. So every node received at least `faulty_num + 1` correct entries of their
1013                // column/row (remember that the bivariate polynomial is symmetric). They can
1014                // reconstruct the full row and in particular value `0` (which no other node knows,
1015                // only the dealer). E.g. let's say nodes `1`, `2` and `4` are honest. Then node
1016                // `m` received three correct entries from that row:
1017                let received: BTreeMap<_, _> = [1, 2, 4]
1018                    .iter()
1019                    .map(|&i| (i, bi_poly.evaluate(m, i)))
1020                    .collect();
1021                let my_row = Poly::interpolate(received)?;
1022                assert_eq!(bi_poly.evaluate(m, 0), my_row.evaluate(0));
1023                assert_eq!(row_poly, my_row);
1024
1025                // The node sums up all values number `0` it received from the different dealer. No
1026                // dealer and no other node knows the sum in the end.
1027                sec_keys[m - 1].add_assign(&my_row.evaluate(Fr::zero()));
1028            }
1029        }
1030
1031        // Each node now adds up all the first values of the rows it received from the different
1032        // dealers (excluding the dealers where fewer than `2 * faulty_num + 1` nodes confirmed).
1033        // The whole first column never gets added up in practice, because nobody has all the
1034        // information. We do it anyway here; entry `0` is the secret key that is not known to
1035        // anyone, neither a dealer, nor a node:
1036        let mut sec_key_set = Poly::zero();
1037        for bi_poly in &bi_polys {
1038            sec_key_set += bi_poly.row(0);
1039        }
1040        for m in 1..=node_num {
1041            assert_eq!(sec_key_set.evaluate(m), sec_keys[m - 1]);
1042        }
1043
1044        // The sum of the first rows of the public commitments is the commitment to the secret key
1045        // set.
1046        let mut sum_commit = Poly::zero().commitment();
1047        for bi_commit in &pub_bi_commits {
1048            sum_commit += bi_commit.row(0);
1049        }
1050        assert_eq!(sum_commit, sec_key_set.commitment());
1051        Ok(())
1052    }
1053
1054    #[test]
1055    fn test_commitment_to_from_bytes() {
1056        let degree = 3;
1057        let mut rng = rand::thread_rng();
1058        let poly = Poly::random(degree, &mut rng);
1059        let commitment = poly.commitment();
1060        // length is fixed to (degree + 1) * 48
1061        let commitment_bytes = commitment.to_bytes();
1062        assert_eq!(commitment_bytes.len(), (degree + 1) * 48);
1063        // the first bytes of the commitment match the first G1
1064        let g1 = commitment.evaluate(0);
1065        let g1_bytes = g1.to_compressed().as_ref().to_vec();
1066        let g1_bytes_size = g1_bytes.len();
1067        for i in 0..g1_bytes_size {
1068            assert_eq!(g1_bytes[i], commitment_bytes[i]);
1069        }
1070        // from bytes gives original commitment
1071        let restored_commitment =
1072            Commitment::from_bytes(commitment_bytes).expect("invalid commitment bytes");
1073        assert_eq!(commitment, restored_commitment);
1074        // for vectors see PublicKeySet
1075    }
1076
1077    #[test]
1078    fn test_bivar_commitment_to_from_bytes() {
1079        let mut rng = rand::thread_rng();
1080        let degree = 3;
1081        let bi_poly = BivarPoly::random(degree, &mut rng);
1082        let bi_commit = bi_poly.commitment();
1083        let commitment = bi_commit.row(0);
1084        // length is fixed by the degree and G1 size
1085        let bi_commit_bytes = bi_commit.to_bytes();
1086        let dp1 = degree + 1;
1087        let sum = dp1 * (dp1 + 1) / 2; // sum 1,2,3,...,dp1
1088        let expected_size = sum * PK_SIZE;
1089        assert_eq!(bi_commit_bytes.len(), expected_size);
1090        // the first bytes of the bivarcommitment match the first commitment public key
1091        let commitment_bytes = commitment.to_bytes();
1092        for i in 0..PK_SIZE {
1093            assert_eq!(commitment_bytes[i], bi_commit_bytes[i]);
1094        }
1095        // from_bytes gives original bivar_commitment
1096        let restored_bi_commit =
1097            BivarCommitment::from_bytes(bi_commit_bytes).expect("invalid bivar commitment bytes");
1098        assert_eq!(bi_commit, restored_bi_commit);
1099        // test rows match
1100        for i in 0..10 {
1101            assert_eq!(bi_commit.row(i), restored_bi_commit.row(i));
1102        }
1103    }
1104
1105    #[test]
1106    fn vectors_bivar_commitment_to_from_bytes() -> Result<()> {
1107        let vectors = vec![
1108            // Plain old Bivar Commitment
1109            vec![
1110                // bivar commitment
1111                "84339010af2fa47ebb8294681b2b61d6ec4c0d6ce595c0a8c57234d348c7be120520a6b710f061196d3fa30323e1bcb48ad0b4a8180ff4ca8888f6e8bd869c43c5b111c2733bab514d826bed4ec10f5ca4aacc838c69cba6a99c14cc59646e69ad14932a863413cbb57d3a0aad84d7ec62276a63990d686058c24fef6e0cc17f9360ac4f8e20725bdabd591ad25c4cf98f9fe02f53ef5876dc6744f18f3462e9a5d0a83d30e949acb2e48ba8ee50d3674a7c4b7d75372983c2fd3e9e9291b0e697993cefe339b05133e2ebb51ff3c63e711969c6d0704631059db9a990183aca270acd5fb82ea78bab983c39290b8c71afd930a68bd2a35bd6fd5e7bc09877dfe7dbafd8f953172016da9cd9e816a09677df3a4c8c2339e530acda235f5feefd",
1112                // commitment row 0
1113                "84339010af2fa47ebb8294681b2b61d6ec4c0d6ce595c0a8c57234d348c7be120520a6b710f061196d3fa30323e1bcb48ad0b4a8180ff4ca8888f6e8bd869c43c5b111c2733bab514d826bed4ec10f5ca4aacc838c69cba6a99c14cc59646e698f9fe02f53ef5876dc6744f18f3462e9a5d0a83d30e949acb2e48ba8ee50d3674a7c4b7d75372983c2fd3e9e9291b0e6",
1114                // commitment row 1
1115                "b9ee0808165ae836d0bf3deeb4e3f3399dde9c6377bf1f0f1dd5096f5f2f61afad3109e9454521832eeac831cad46ef48a0974487ffb0e5343f387e39d5f40312522ca0d90e51d20c89ff7347a724705f8d4429bb6e27100e603195cf89d38a1a4346721f22704f18b2a9fa001944ebe48b8e08eca91c06c23f13061e5929503d67549526591693da175125eb2d17178",
1116            ]
1117        ];
1118        for vector in vectors {
1119            // read bivar commitment
1120            let bi_commit_bytes =
1121                hex::decode(vector[0]).map_err(|err| eyre!("invalid msg hex bytes: {}", err))?;
1122            let bi_commit = BivarCommitment::from_bytes(bi_commit_bytes)
1123                .expect("invalid bivar commitment bytes");
1124            // check row 0
1125            let row_0 = bi_commit.row(0);
1126            let row_0_hex = &format!("{}", HexFmt(&row_0.to_bytes()));
1127            assert_eq!(row_0_hex, vector[1]);
1128            // check row 1
1129            let row_1 = bi_commit.row(1);
1130            let row_1_hex = &format!("{}", HexFmt(&row_1.to_bytes()));
1131            assert_eq!(row_1_hex, vector[2]);
1132        }
1133
1134        Ok(())
1135    }
1136
1137    #[test]
1138    fn test_bivar_commitment_to_from_bytes_large_degree() {
1139        // The overall size increases rapidly, size is sum(1..degree+1)
1140        let mut rng = rand::thread_rng();
1141        let degree = 9; // TODO pick a less magic value here
1142        let bi_poly = BivarPoly::random(degree, &mut rng);
1143        let bi_commit = bi_poly.commitment();
1144        let bi_commit_bytes = bi_commit.to_bytes();
1145        let dp1 = degree + 1;
1146        let sum = dp1 * (dp1 + 1) / 2; // sum of 1,2,3,...,dp1
1147        let expected_size = sum * 48;
1148        assert_eq!(bi_commit_bytes.len(), expected_size);
1149    }
1150
1151    #[test]
1152    fn test_poly_to_from_bytes() {
1153        let degree = 3;
1154        let mut rng = rand::thread_rng();
1155        let poly = Poly::random(degree, &mut rng);
1156        // length is fixed to (degree + 1) * 32
1157        let poly_bytes = poly.to_bytes();
1158        assert_eq!(poly_bytes.len(), (degree + 1) * 32);
1159        // the first bytes of the poly match the first Fr
1160        let fr = poly.evaluate(0);
1161        let fr_bytes = fr.to_bytes_be();
1162        let fr_bytes_size = fr_bytes.len();
1163        for i in 0..fr_bytes_size {
1164            assert_eq!(fr_bytes[i], poly_bytes[i]);
1165        }
1166        // from bytes gives original poly
1167        let restored_poly = Poly::from_bytes(poly_bytes).expect("invalid poly bytes");
1168        assert_eq!(poly, restored_poly);
1169        // for vectors see SecretKeySet
1170    }
1171
1172    #[test]
1173    fn test_bivar_poly_to_from_bytes() {
1174        let degree = 3;
1175        let mut rng = rand::thread_rng();
1176        let bi_poly = BivarPoly::random(degree, &mut rng);
1177        // length is fixed by the degree and Fr size
1178        let bi_poly_bytes = bi_poly.to_bytes();
1179        let dp1 = degree + 1;
1180        let sum = dp1 * (dp1 + 1) / 2; // sum 1,2,3,...,dp1
1181        let expected_size = sum * SK_SIZE;
1182        assert_eq!(bi_poly_bytes.len(), expected_size);
1183        // the first bytes of the bivarypoly match the first poly
1184        let poly = bi_poly.row(0);
1185        let poly_bytes = poly.to_bytes();
1186        for i in 0..SK_SIZE {
1187            assert_eq!(poly_bytes[i], bi_poly_bytes[i]);
1188        }
1189        // from_bytes gives original bivar_poly
1190        let restored_bi_poly = BivarPoly::from_bytes(bi_poly_bytes).expect("invalid bipoly bytes");
1191        // cannot test equality for bi_poly so test using reveal
1192        assert_eq!(bi_poly.reveal(), restored_bi_poly.reveal());
1193        // test rows match
1194        for i in 0..10 {
1195            assert_eq!(bi_poly.row(i), restored_bi_poly.row(i));
1196        }
1197    }
1198
1199    #[test]
1200    fn test_bivar_poly_to_from_bytes_large_degree() {
1201        let mut rng = rand::thread_rng();
1202        let degree = 9; // TODO pick a less magic value here
1203        let bi_poly = BivarPoly::random(degree, &mut rng);
1204        let bi_poly_bytes = bi_poly.to_bytes();
1205        let dp1 = degree + 1;
1206        let sum = dp1 * (dp1 + 1) / 2; // sum of 1,2,3,...,dp1
1207        let expected_size = sum * SK_SIZE;
1208        assert_eq!(bi_poly_bytes.len(), expected_size);
1209    }
1210
1211    #[test]
1212    fn vectors_bivar_poly_to_from_bytes() -> Result<()> {
1213        let vectors = vec![
1214            // Plain old Bivar Poly
1215            // Sourced from BivarPoly::reveal and Poly::reveal
1216            vec![
1217                // bivar poly
1218                "016088115a0e8748a29b4bd8dd930692b86241405844f0bbd2fcafb6b4f03880222caa92f7eea1dd8a0e4f2d1e62672a5c12dfcb86199515d4a450ed7921d7ca1407dec2b53c472ffcaf4dbfcd8fe5089cb64a7b5ce7e38655aa97400a3bc1bb4045160918bad84c11afc883e514321009a113cb9bc3b7b941486ec3ca4a273565951ee649287a5809bc0036b8ffee73eb51dc4e3f35e7578169e66823da066672c4060b5f46f15eb1a7c253bb564d9ad2e9c12182a0df61499788817d21a133",
1219                // row 0 poly
1220                "016088115a0e8748a29b4bd8dd930692b86241405844f0bbd2fcafb6b4f03880222caa92f7eea1dd8a0e4f2d1e62672a5c12dfcb86199515d4a450ed7921d7ca4045160918bad84c11afc883e514321009a113cb9bc3b7b941486ec3ca4a2735",
1221                // row 1 poly
1222                "63d248ad6ab801723e596389e1099fcd1e1634d77a223d8ae8e96f67f85c377f27dc00e8ccb5e61d5d3fc51b9b5062a1905d6292223903f4abb8ce96a7379fea30c2ec546def4972669fdafe4626be14206169355d9dc6740c49ddaf6b45cecc",
1223            ],
1224        ];
1225        for vector in vectors {
1226            // read BivarPoly from hex
1227            let bi_poly_bytes =
1228                hex::decode(vector[0]).map_err(|err| eyre!("invalid msg hex bytes: {}", err))?;
1229            let bi_poly = BivarPoly::from_bytes(bi_poly_bytes).expect("invalid bipoly bytes");
1230            // check row 0
1231            let row_0 = bi_poly.row(0);
1232            let row_0_hex = &format!("{}", HexFmt(&row_0.to_bytes()));
1233            assert_eq!(row_0_hex, vector[1]);
1234            // check row 1
1235            let row_1 = bi_poly.row(1);
1236            let row_1_hex = &format!("{}", HexFmt(&row_1.to_bytes()));
1237            assert_eq!(row_1_hex, vector[2]);
1238        }
1239
1240        Ok(())
1241    }
1242}