Skip to content

Commit 4245085

Browse files
authored
[3/4 EIP 4844 in inserter circuit] Add gadget to polynomial evaluation using barycentric formula (#13)
prover: barycentric: implement barycentric formula gadget The new `barycentric` package adds `CalculateBarycentricFormula`. The function implements the evaluation of a polynomial in evaluation form at a point outside the domain, using barycentric interpolation. This function follows implementation by Dankrad Feist, as described in his blog post: https://dankradfeist.de/ethereum/2021/06/18/pcs-multiproofs.html. Another helper package is added - `field_utils`. It is a place for helper gadgets for field elements manipulations. It contains one function `Exp` to calculate field element's power of n, where n is an integer (not a field element). Signed-off-by: Wojciech Zmuda <[email protected]>
1 parent 9666200 commit 4245085

File tree

5 files changed

+285
-0
lines changed

5 files changed

+285
-0
lines changed

prover/barycentric/barycentric.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package barycentric
2+
3+
import (
4+
"github.com/consensys/gnark/std/math/emulated"
5+
6+
"worldcoin/gnark-mbu/prover/field_utils"
7+
)
8+
9+
// CalculateBarycentricFormula implements the evaluation of a polynomial in evaluation form at a point outside the
10+
// domain, using barycentric interpolation. This function follows the formulation by Dankrad Feist, as described
11+
// in his blog post: https://dankradfeist.de/ethereum/2021/06/18/pcs-multiproofs.html.
12+
//
13+
// The formula used for calculation is: ((z^d - 1) / d) * Σ((f_i * ω^i) / (z - ω^i)) for i=0 to d-1,
14+
// where z is the target evaluation point, d is the degree of the polynomial, f_i are the polynomial coefficients,
15+
// and ω^i are the domain elements.
16+
//
17+
// field - reference to the emulated field operations structure, used for arithmetic operations within the specified
18+
// field.
19+
// omegasToI - slice containing the powers of the primitive root of unity ω, raised to the power of index i,
20+
// representing the domain elements.
21+
// yNodes - slice containing the polynomial coefficients or the values of the polynomial at the domain elements.
22+
// targetPoint - point outside the domain at which the polynomial is to be evaluated.
23+
func CalculateBarycentricFormula[T emulated.FieldParams](
24+
field *emulated.Field[T], omegasToI, yNodes []emulated.Element[T], targetPoint emulated.Element[T],
25+
) emulated.Element[T] {
26+
27+
polynomialDegree := len(yNodes)
28+
29+
// First term: (z^d - 1) / d
30+
zToD := field_utils.Exp(field, &targetPoint, polynomialDegree)
31+
firstTerm := *field.Sub(zToD, field.One())
32+
d := emulated.ValueOf[T](polynomialDegree)
33+
firstTerm = *field.Div(&firstTerm, &d)
34+
35+
// Second term: Σ(f_i * ω^i)/(z - ω^i) from i=0 to d-1
36+
secondTerm := field.Zero()
37+
for i := range polynomialDegree {
38+
numerator := *field.Mul(&yNodes[i], &omegasToI[i])
39+
denominator := *field.Sub(&targetPoint, &omegasToI[i])
40+
term := *field.Div(&numerator, &denominator)
41+
secondTerm = field.Add(secondTerm, &term)
42+
}
43+
44+
return *field.Mul(&firstTerm, secondTerm)
45+
}
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
package barycentric
2+
3+
import (
4+
"math"
5+
"math/big"
6+
"testing"
7+
8+
"github.com/consensys/gnark-crypto/ecc"
9+
"github.com/consensys/gnark/backend"
10+
"github.com/consensys/gnark/frontend"
11+
"github.com/consensys/gnark/std/math/emulated"
12+
"github.com/consensys/gnark/test"
13+
)
14+
15+
type BarycentricCircuit[T emulated.FieldParams] struct {
16+
Omega big.Int // ω
17+
PolynomialDegree int
18+
19+
// Inputs (private)
20+
YNodes []emulated.Element[T] // len(YNodes) == PolynomialDegree
21+
TargetPoint emulated.Element[T]
22+
23+
// Output
24+
InterpolatedPoint emulated.Element[T] `gnark:",public"`
25+
}
26+
27+
func (circuit *BarycentricCircuit[T]) Define(api frontend.API) error {
28+
field, err := emulated.NewField[T](api)
29+
if err != nil {
30+
return err
31+
}
32+
33+
api.AssertIsEqual(len(circuit.YNodes), circuit.PolynomialDegree)
34+
35+
omegasToI := make([]emulated.Element[T], circuit.PolynomialDegree)
36+
omegaToI := big.NewInt(1)
37+
for i := range circuit.PolynomialDegree {
38+
omegasToI[i] = emulated.ValueOf[T](omegaToI)
39+
omegaToI.Mul(omegaToI, &circuit.Omega)
40+
}
41+
42+
// Method under test
43+
interpolatedPointCalculated := CalculateBarycentricFormula[T](field, omegasToI, circuit.YNodes, circuit.TargetPoint)
44+
45+
field.AssertIsEqual(&circuit.InterpolatedPoint, &interpolatedPointCalculated)
46+
47+
return nil
48+
}
49+
50+
func setupTestEnvironment(polynomialDegree int) (*big.Int, *big.Int) {
51+
// The test assumes BLS12381Fr field and a certain polynomial degree
52+
modulus, _ := new(big.Int).SetString(
53+
"52435875175126190479447740508185965837690552500527637822603658699938581184513", 10,
54+
)
55+
56+
// For polynomial degree d = 4096 = 2^12:
57+
// ω^(2^32) = ω^(2^20 * 2^12)
58+
// Calculate ω^20 starting with root of unity of 2^32 degree
59+
omega, _ := new(big.Int).SetString(
60+
"10238227357739495823651030575849232062558860180284477541189508159991286009131", 10,
61+
)
62+
polynomialDegreeExp := int(math.Log2(float64(polynomialDegree)))
63+
omegaExpExp := 32 // ω^(2^32)
64+
for range omegaExpExp - polynomialDegreeExp {
65+
omega.Mul(omega, omega)
66+
omega.Mod(omega, modulus)
67+
}
68+
69+
return omega, modulus
70+
}
71+
72+
func TestCalculateBarycentricFormula(t *testing.T) {
73+
type Fr = emulated.BLS12381Fr
74+
const polynomialDegree = 4096
75+
omega, modulus := setupTestEnvironment(polynomialDegree)
76+
77+
// Test cases
78+
type PolynomialTestCase[T emulated.FieldParams] struct {
79+
Name string
80+
CalculateYNodes func(omega *big.Int, modulus *big.Int, polynomialDegree int) []emulated.Element[T]
81+
TargetPoint int64
82+
InterpolatedPoint int64
83+
}
84+
tests := []PolynomialTestCase[Fr]{
85+
{
86+
Name: "f(x) = x^3",
87+
CalculateYNodes: func(omega *big.Int, modulus *big.Int, polynomialDegree int) []emulated.Element[Fr] {
88+
y := make([]emulated.Element[Fr], polynomialDegree)
89+
for i := range y {
90+
res := new(big.Int).Exp(omega, big.NewInt(int64(i*3)), modulus)
91+
y[i] = emulated.ValueOf[Fr](res)
92+
}
93+
return y
94+
},
95+
TargetPoint: 3,
96+
InterpolatedPoint: 27,
97+
},
98+
{
99+
Name: "f(x) = 3x^7 + 2x^4 + 4x + 20",
100+
CalculateYNodes: func(omega *big.Int, modulus *big.Int, polynomialDegree int) []emulated.Element[Fr] {
101+
y := make([]emulated.Element[Fr], polynomialDegree)
102+
for i := range y {
103+
a := new(big.Int).Exp(omega, big.NewInt(int64(i*7)), modulus)
104+
a.Mul(a, big.NewInt(3))
105+
106+
b := new(big.Int).Exp(omega, big.NewInt(int64(i*4)), modulus)
107+
b.Mul(b, big.NewInt(2))
108+
109+
c := new(big.Int).Exp(omega, big.NewInt(int64(i)), modulus)
110+
c.Mul(c, big.NewInt(4))
111+
112+
res := new(big.Int).Add(a, b)
113+
res.Add(res, c)
114+
res.Add(res, big.NewInt(20))
115+
res.Mod(res, modulus)
116+
117+
y[i] = emulated.ValueOf[Fr](res)
118+
}
119+
return y
120+
},
121+
TargetPoint: 3,
122+
InterpolatedPoint: 6755,
123+
},
124+
}
125+
126+
for _, tc := range tests {
127+
assert := test.NewAssert(t)
128+
assert.Run(
129+
func(a *test.Assert) {
130+
circuit := BarycentricCircuit[Fr]{
131+
Omega: *omega,
132+
PolynomialDegree: polynomialDegree,
133+
YNodes: make([]emulated.Element[Fr], polynomialDegree),
134+
}
135+
136+
assignment := BarycentricCircuit[Fr]{
137+
YNodes: tc.CalculateYNodes(omega, modulus, polynomialDegree),
138+
TargetPoint: emulated.ValueOf[Fr](tc.TargetPoint),
139+
InterpolatedPoint: emulated.ValueOf[Fr](tc.InterpolatedPoint),
140+
}
141+
142+
assert.CheckCircuit(
143+
&circuit, test.WithBackends(backend.GROTH16), test.WithCurves(ecc.BN254),
144+
test.WithValidAssignment(&assignment),
145+
)
146+
}, tc.Name,
147+
)
148+
}
149+
}

prover/field_utils/doc.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
package field_utils
2+
3+
// Package field_utils contains convenience functions to manipulate prime field elements.

prover/field_utils/field_exp.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package field_utils
2+
3+
import "github.com/consensys/gnark/std/math/emulated"
4+
5+
// Exp raises base to the exponent in the given prime field.
6+
//
7+
// field - given prime field where base and the result belong.
8+
// base - the number in the field to be risen to the given exponent.
9+
// exponent - an integer to rise base to.
10+
func Exp[T emulated.FieldParams](
11+
field *emulated.Field[T], base *emulated.Element[T], exponent int,
12+
) *emulated.Element[T] {
13+
res := field.One()
14+
for exponent > 0 {
15+
if exponent%2 == 1 {
16+
res = field.Mul(res, base)
17+
}
18+
base = field.Mul(base, base)
19+
exponent /= 2
20+
}
21+
return res
22+
}

prover/field_utils/field_exp_test.go

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package field_utils
2+
3+
import (
4+
"fmt"
5+
"math/big"
6+
"math/rand"
7+
"testing"
8+
9+
"github.com/consensys/gnark-crypto/ecc"
10+
"github.com/consensys/gnark/backend"
11+
"github.com/consensys/gnark/frontend"
12+
"github.com/consensys/gnark/std/math/emulated"
13+
"github.com/consensys/gnark/test"
14+
)
15+
16+
type ExpCircuit[T emulated.FieldParams] struct {
17+
Base emulated.Element[T]
18+
Exp int
19+
Res emulated.Element[T]
20+
}
21+
22+
func (c *ExpCircuit[T]) Define(api frontend.API) error {
23+
field, err := emulated.NewField[T](api)
24+
if err != nil {
25+
return err
26+
}
27+
28+
// Function under test
29+
calculatedRes := Exp[T](field, &c.Base, c.Exp)
30+
31+
field.AssertIsEqual(calculatedRes, &c.Res)
32+
33+
return nil
34+
}
35+
36+
func randomPower() (int, int, *big.Int) {
37+
base := rand.Intn(16)
38+
exponent := rand.Intn(16)
39+
result := new(big.Int).Exp(big.NewInt(int64(base)), big.NewInt(int64(exponent)), nil)
40+
return base, exponent, result
41+
}
42+
43+
func TestExp(t *testing.T) {
44+
assert := test.NewAssert(t)
45+
46+
for range 16 { // Arbitrary choice of number of tests
47+
base, exp, want := randomPower()
48+
circuit := ExpCircuit[emulated.BLS12381Fr]{
49+
Exp: exp,
50+
}
51+
52+
assignment := ExpCircuit[emulated.BLS12381Fr]{
53+
Base: emulated.ValueOf[emulated.BLS12381Fr](base),
54+
Res: emulated.ValueOf[emulated.BLS12381Fr](want),
55+
}
56+
57+
t.Run(
58+
fmt.Sprintf("%d^%d", base, exp), func(t *testing.T) {
59+
assert.CheckCircuit(
60+
&circuit, test.WithBackends(backend.GROTH16), test.WithCurves(ecc.BN254),
61+
test.WithValidAssignment(&assignment),
62+
)
63+
},
64+
)
65+
}
66+
}

0 commit comments

Comments
 (0)