Skip to content

Commit 2901247

Browse files
committed
Adds polynomials and Lagrange polynomials.
1 parent c8971c0 commit 2901247

File tree

2 files changed

+224
-0
lines changed

2 files changed

+224
-0
lines changed

math/polynomial/polynomial.go

+123
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
// Package polynomial provides representations of polynomials over the scalars
2+
// of a group.
3+
package polynomial
4+
5+
import (
6+
"fmt"
7+
8+
"github.com/cloudflare/circl/group"
9+
)
10+
11+
// Polynomial stores a polynomial over the set of scalars of a group.
12+
type Polynomial struct {
13+
// Internal representation is in polynomial basis:
14+
// Thus,
15+
// p(x) = \sum_i^k c[i] x^i,
16+
// where k = len(c)-1 is the degree of the polynomial.
17+
c []group.Scalar
18+
}
19+
20+
// New creates a new polynomial given its coefficients in ascending order.
21+
// Thus,
22+
// p(x) = \sum_i^k c[i] x^i,
23+
// where k = len(c)-1 is the degree of the polynomial.
24+
func New(coeffs []group.Scalar) Polynomial {
25+
if len(coeffs) == 0 {
26+
panic("polynomial: invalid length")
27+
}
28+
return Polynomial{coeffs}
29+
}
30+
31+
func (p Polynomial) Degree() uint { return uint(len(p.c) - 1) }
32+
33+
func (p Polynomial) Evaluate(x group.Scalar) group.Scalar {
34+
l := len(p.c)
35+
px := p.c[l-1].Copy()
36+
for i := l - 2; i >= 0; i-- {
37+
px.Mul(px, x)
38+
px.Add(px, p.c[i])
39+
}
40+
return px
41+
}
42+
43+
// LagrangePolynomial stores a Lagrange polynomial over the set of scalars of a group.
44+
type LagrangePolynomial struct {
45+
// Internal representation is in Lagrange basis:
46+
// Thus,
47+
// p(x) = \sum_i^k y[i] L_j(x), where k is the degree of the polynomial,
48+
// L_j(x) = \prod_i^k (x-x[i])/(x[j]-x[i]),
49+
// y[i] = p(x[i]), and
50+
// all x[i] are different.
51+
g group.Group
52+
x, y []group.Scalar
53+
}
54+
55+
// NewLagrangePolynomial creates a polynomial in Lagrange basis given a list
56+
// of nodes (x) and values (y), such that:
57+
// p(x) = \sum_i^k y[i] L_j(x), where k is the degree of the polynomial,
58+
// L_j(x) = \prod_i^k (x-x[i])/(x[j]-x[i]),
59+
// y[i] = p(x[i]), and
60+
// all x[i] are different.
61+
// It panics if one of these conditions does not hold.
62+
func NewLagrangePolynomial(g group.Group, x, y []group.Scalar) LagrangePolynomial {
63+
if len(x) != len(y) {
64+
panic("lagrange: invalid length")
65+
}
66+
if !isAllDifferent(x) {
67+
panic("lagrange: nodes must be different")
68+
}
69+
70+
return LagrangePolynomial{g, x, y}
71+
}
72+
73+
func (l LagrangePolynomial) Degree() uint { return uint(len(l.x) - 1) }
74+
75+
func (l LagrangePolynomial) Evaluate(x group.Scalar) group.Scalar {
76+
px := l.g.NewScalar()
77+
tmp := l.g.NewScalar()
78+
for i := range l.x {
79+
LjX := baseRatio(uint(i), l.x, x)
80+
tmp.Mul(l.y[i], LjX)
81+
px.Add(px, tmp)
82+
}
83+
84+
return px
85+
}
86+
87+
// LagrangeBase returns the j-th Lagrange polynomial base evaluated at x.
88+
// Thus, L_j(x) = \prod (x - x[i]) / (x[j] - x[i]) for 0 <= i < k, and i != j.
89+
func LagrangeBase(jth uint, xi []group.Scalar, x group.Scalar) group.Scalar {
90+
if jth >= uint(len(xi)) {
91+
panic("lagrange: invalid index")
92+
}
93+
return baseRatio(jth, xi, x)
94+
}
95+
96+
func baseRatio(jth uint, xi []group.Scalar, x group.Scalar) group.Scalar {
97+
num := x.Copy()
98+
num.SetUint64(1)
99+
den := x.Copy()
100+
den.SetUint64(1)
101+
102+
tmp := x.Copy()
103+
for i := range xi {
104+
if uint(i) != jth {
105+
num.Mul(num, tmp.Sub(x, xi[i]))
106+
den.Mul(den, tmp.Sub(xi[jth], xi[i]))
107+
}
108+
}
109+
110+
return num.Mul(num, den.Inv(den))
111+
}
112+
113+
func isAllDifferent(x []group.Scalar) bool {
114+
m := make(map[string]struct{})
115+
for i := range x {
116+
k := x[i].(fmt.Stringer).String()
117+
if _, exists := m[k]; exists {
118+
return false
119+
}
120+
m[k] = struct{}{}
121+
}
122+
return true
123+
}

math/polynomial/polynomial_test.go

+101
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
package polynomial_test
2+
3+
import (
4+
"testing"
5+
6+
"github.com/cloudflare/circl/group"
7+
"github.com/cloudflare/circl/internal/test"
8+
"github.com/cloudflare/circl/math/polynomial"
9+
)
10+
11+
func TestPolyEval(t *testing.T) {
12+
g := group.P256
13+
c := []group.Scalar{
14+
g.NewScalar(),
15+
g.NewScalar(),
16+
g.NewScalar(),
17+
}
18+
c[0].SetUint64(5)
19+
c[1].SetUint64(5)
20+
c[2].SetUint64(2)
21+
p := polynomial.New(c)
22+
23+
x := g.NewScalar()
24+
x.SetUint64(10)
25+
26+
got := p.Evaluate(x)
27+
want := g.NewScalar()
28+
want.SetUint64(255)
29+
if !got.IsEqual(want) {
30+
test.ReportError(t, got, want)
31+
}
32+
}
33+
34+
func TestLagrange(t *testing.T) {
35+
g := group.P256
36+
c := []group.Scalar{
37+
g.NewScalar(),
38+
g.NewScalar(),
39+
g.NewScalar(),
40+
}
41+
c[0].SetUint64(1234)
42+
c[1].SetUint64(166)
43+
c[2].SetUint64(94)
44+
p := polynomial.New(c)
45+
46+
x := []group.Scalar{g.NewScalar(), g.NewScalar(), g.NewScalar()}
47+
x[0].SetUint64(2)
48+
x[1].SetUint64(4)
49+
x[2].SetUint64(5)
50+
51+
y := []group.Scalar{}
52+
for i := range x {
53+
y = append(y, p.Evaluate(x[i]))
54+
}
55+
56+
zero := g.NewScalar()
57+
l := polynomial.NewLagrangePolynomial(g, x, y)
58+
test.CheckOk(l.Degree() == p.Degree(), "bad degree", t)
59+
60+
got := l.Evaluate(zero)
61+
want := p.Evaluate(zero)
62+
63+
if !got.IsEqual(want) {
64+
test.ReportError(t, got, want)
65+
}
66+
67+
// Test Kronecker's delta of LagrangeBase.
68+
// Thus:
69+
// L_j(x[i]) = { 1, if i == j;
70+
// { 0, otherwise.
71+
one := g.NewScalar()
72+
one.SetUint64(1)
73+
for j := range x {
74+
for i := range x {
75+
got := polynomial.LagrangeBase(uint(j), x, x[i])
76+
77+
if i == j {
78+
want = one
79+
} else {
80+
want = zero
81+
}
82+
83+
if !got.IsEqual(want) {
84+
test.ReportError(t, got, want)
85+
}
86+
}
87+
}
88+
89+
// Test that inputs are different length
90+
err := test.CheckPanic(func() { polynomial.NewLagrangePolynomial(g, x, y[:1]) })
91+
test.CheckNoErr(t, err, "should panic")
92+
93+
// Test that nodes must be different.
94+
x[0].Set(x[1])
95+
err = test.CheckPanic(func() { polynomial.NewLagrangePolynomial(g, x, y) })
96+
test.CheckNoErr(t, err, "should panic")
97+
98+
// Test LagrangeBase wrong index
99+
err = test.CheckPanic(func() { polynomial.LagrangeBase(10, x, zero) })
100+
test.CheckNoErr(t, err, "should panic")
101+
}

0 commit comments

Comments
 (0)