github.com/cloudflare/circl@v1.5.0/math/polynomial/polynomial.go (about)

     1  // Package polynomial provides representations of polynomials over the scalars
     2  // of a group.
     3  package polynomial
     4  
     5  import "github.com/cloudflare/circl/group"
     6  
     7  // Polynomial stores a polynomial over the set of scalars of a group.
     8  type Polynomial struct {
     9  	// Internal representation is in polynomial basis:
    10  	// Thus,
    11  	//     p(x) = \sum_i^k c[i] x^i,
    12  	// where k = len(c)-1 is the degree of the polynomial.
    13  	c []group.Scalar
    14  }
    15  
    16  // New creates a new polynomial given its coefficients in ascending order.
    17  // Thus,
    18  //
    19  //	p(x) = \sum_i^k c[i] x^i,
    20  //
    21  // where k = len(c)-1 is the degree of the polynomial.
    22  //
    23  // The zero polynomial has degree equal to -1 and can be instantiated passing
    24  // nil to New.
    25  func New(coeffs []group.Scalar) (p Polynomial) {
    26  	if l := len(coeffs); l != 0 {
    27  		p.c = make([]group.Scalar, l)
    28  		for i := range coeffs {
    29  			p.c[i] = coeffs[i].Copy()
    30  		}
    31  	}
    32  
    33  	return
    34  }
    35  
    36  // Degree returns the degree of the polynomial. The zero polynomial has degree
    37  // equal to -1.
    38  func (p Polynomial) Degree() int {
    39  	i := len(p.c) - 1
    40  	for i > 0 && p.c[i].IsZero() {
    41  		i--
    42  	}
    43  	return i
    44  }
    45  
    46  // Evaluate returns the evaluation of p on x.
    47  func (p Polynomial) Evaluate(x group.Scalar) group.Scalar {
    48  	px := x.Group().NewScalar()
    49  	if l := len(p.c); l != 0 {
    50  		px.Set(p.c[l-1])
    51  		for i := l - 2; i >= 0; i-- {
    52  			px.Mul(px, x)
    53  			px.Add(px, p.c[i])
    54  		}
    55  	}
    56  	return px
    57  }
    58  
    59  // Coefficient returns a deep-copy of the n-th polynomial's coefficient.
    60  // Note coefficients are sorted in ascending order with respect to the degree.
    61  func (p Polynomial) Coefficient(n uint) group.Scalar {
    62  	if int(n) >= len(p.c) {
    63  		panic("polynomial: invalid index for coefficient")
    64  	}
    65  	return p.c[n].Copy()
    66  }
    67  
    68  // LagrangePolynomial stores a Lagrange polynomial over the set of scalars of a group.
    69  type LagrangePolynomial struct {
    70  	// Internal representation is in Lagrange basis:
    71  	// Thus,
    72  	//     p(x) = \sum_i^k y[i] L_j(x), where k is the degree of the polynomial,
    73  	//     L_j(x) = \prod_i^k (x-x[i])/(x[j]-x[i]),
    74  	//     y[i] = p(x[i]), and
    75  	//     all x[i] are different.
    76  	x, y []group.Scalar
    77  }
    78  
    79  // NewLagrangePolynomial creates a polynomial in Lagrange basis given a list
    80  // of nodes (x) and values (y), such that:
    81  //
    82  //	p(x) = \sum_i^k y[i] L_j(x), where k is the degree of the polynomial,
    83  //	L_j(x) = \prod_i^k (x-x[i])/(x[j]-x[i]),
    84  //	y[i] = p(x[i]), and
    85  //	all x[i] are different.
    86  //
    87  // It panics if one of these conditions does not hold.
    88  //
    89  // The zero polynomial has degree equal to -1 and can be instantiated passing
    90  // (nil,nil) to NewLagrangePolynomial.
    91  func NewLagrangePolynomial(x, y []group.Scalar) (l LagrangePolynomial) {
    92  	if len(x) != len(y) {
    93  		panic("lagrange: invalid length")
    94  	}
    95  
    96  	if !areAllDifferent(x) {
    97  		panic("lagrange: x[i] must be different")
    98  	}
    99  
   100  	if n := len(x); n != 0 {
   101  		l.x, l.y = make([]group.Scalar, n), make([]group.Scalar, n)
   102  		for i := range x {
   103  			l.x[i], l.y[i] = x[i].Copy(), y[i].Copy()
   104  		}
   105  	}
   106  
   107  	return
   108  }
   109  
   110  func (l LagrangePolynomial) Degree() int { return len(l.x) - 1 }
   111  
   112  func (l LagrangePolynomial) Evaluate(x group.Scalar) group.Scalar {
   113  	px := x.Group().NewScalar()
   114  	tmp := x.Group().NewScalar()
   115  	for i := range l.x {
   116  		LjX := baseRatio(uint(i), l.x, x)
   117  		tmp.Mul(l.y[i], LjX)
   118  		px.Add(px, tmp)
   119  	}
   120  
   121  	return px
   122  }
   123  
   124  // LagrangeBase returns the j-th Lagrange polynomial base evaluated at x.
   125  // Thus, L_j(x) = \prod (x - x[i]) / (x[j] - x[i]) for 0 <= i < k, and i != j.
   126  func LagrangeBase(jth uint, xi []group.Scalar, x group.Scalar) group.Scalar {
   127  	if jth >= uint(len(xi)) {
   128  		panic("lagrange: invalid index")
   129  	}
   130  	return baseRatio(jth, xi, x)
   131  }
   132  
   133  func baseRatio(jth uint, xi []group.Scalar, x group.Scalar) group.Scalar {
   134  	num := x.Copy()
   135  	num.SetUint64(1)
   136  	den := x.Copy()
   137  	den.SetUint64(1)
   138  
   139  	tmp := x.Copy()
   140  	for i := range xi {
   141  		if uint(i) != jth {
   142  			num.Mul(num, tmp.Sub(x, xi[i]))
   143  			den.Mul(den, tmp.Sub(xi[jth], xi[i]))
   144  		}
   145  	}
   146  
   147  	return num.Mul(num, den.Inv(den))
   148  }
   149  
   150  func areAllDifferent(x []group.Scalar) bool {
   151  	m := make(map[string]struct{})
   152  	for i := range x {
   153  		k, err := x[i].MarshalBinary()
   154  		if err != nil {
   155  			panic(err)
   156  		}
   157  		if _, exists := m[string(k)]; exists {
   158  			return false
   159  		}
   160  		m[string(k)] = struct{}{}
   161  	}
   162  	return true
   163  }