Skip to content

Commit

Permalink
Merge pull request #361 from ConsenSys/feat/polynomial
Browse files Browse the repository at this point in the history
Feat/polynomial
  • Loading branch information
Tabaie committed Sep 9, 2022
2 parents adaf096 + 23b4f4a commit 21aca78
Show file tree
Hide file tree
Showing 3 changed files with 238 additions and 1 deletion.
2 changes: 1 addition & 1 deletion std/hints.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ func RegisterHints() {
}

func registerHints() {
// note that importing these packages may already triggers a call to hint.Register(...)
// note that importing these packages may already trigger a call to hint.Register(...)
hint.Register(sw_bls24315.DecomposeScalarG1)
hint.Register(sw_bls12377.DecomposeScalarG1)
hint.Register(sw_bls24315.DecomposeScalarG2)
Expand Down
116 changes: 116 additions & 0 deletions std/polynomial/polynomial.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
package polynomial

import (
"github.com/consensys/gnark/frontend"
"math/bits"
)

type Polynomial []frontend.Variable
type MultiLin []frontend.Variable

// Eval assumes len(m) = 1 << len(at)
func (m MultiLin) Eval(api frontend.API, at []frontend.Variable) frontend.Variable {

eqs := make([]frontend.Variable, len(m))
eqs[0] = 1
for i, rI := range at {
prevSize := 1 << i
oneMinusRI := api.Sub(1, rI)
for j := prevSize - 1; j >= 0; j-- {
eqs[2*j+1] = api.Mul(rI, eqs[j])
eqs[2*j] = api.Mul(oneMinusRI, eqs[j])
}
}

evaluation := frontend.Variable(0)
for j := range m {
evaluation = api.Add(
evaluation,
api.Mul(eqs[j], m[j]),
)
}
return evaluation
}

func (m MultiLin) NumVars() int {
return bits.TrailingZeros(uint(len(m)))
}

func (p Polynomial) Eval(api frontend.API, at frontend.Variable) (pAt frontend.Variable) {
pAt = 0

for i := len(p) - 1; i >= 0; i-- {
pAt = api.Add(pAt, p[i])
if i != 0 {
pAt = api.Mul(pAt, at)
}
}

return
}

// negFactorial returns (-n)(-n+1)...(-2)(-1)
// There are more efficient algorithms, but we are talking small values here so it doesn't matter
func negFactorial(n int) int {
result := n
n = -n
for n++; n < -1; n++ {
result *= n
}
return result
}

// computeDeltaAtNaive brute forces the computation of the δᵢ(at)
func computeDeltaAtNaive(api frontend.API, at frontend.Variable, valuesLen int) (deltaAt []frontend.Variable) {
deltaAt = make([]frontend.Variable, valuesLen)
atMinus := make([]frontend.Variable, valuesLen)
for i := range atMinus {
atMinus[i] = api.Sub(at, i)
}
factInv := api.Inverse(negFactorial(valuesLen - 1))
for i := range deltaAt {
deltaAt[i] = factInv
for j := range atMinus {
if i != j {
deltaAt[i] = api.Mul(deltaAt[i], atMinus[j])
}
}

if i+1 < len(deltaAt) {
factAdjustment := api.DivUnchecked(i+1-valuesLen, i+1)
factInv = api.Mul(factAdjustment, factInv)
}
}
return
}

// InterpolateLDEOnRange fits a polynomial f of degree len(values)-1 such that f(i) = values[i] whenever defined. Returns f(at)
func InterpolateLDEOnRange(api frontend.API, at frontend.Variable, values []frontend.Variable) frontend.Variable {
deltaAt := computeDeltaAtNaive(api, at, len(values))

res := frontend.Variable(0)

for i, c := range values {
res = api.Add(res,
api.Mul(c, deltaAt[i]),
)
}

return res
}

// EvalEq returns Πⁿ₁ Eq(xᵢ, yᵢ) = Πⁿ₁ xᵢyᵢ + (1-xᵢ)(1-yᵢ) = Πⁿ₁ (1 + 2xᵢyᵢ - xᵢ - yᵢ). Is assumes len(x) = len(y) =: n
func EvalEq(api frontend.API, x, y []frontend.Variable) (eq frontend.Variable) {

eq = 1
for i := range x {
next := api.Mul(x[i], y[i])
next = api.Add(next, next)
next = api.Add(next, 1)
next = api.Sub(next, x[i])
next = api.Sub(next, y[i])

eq = api.Mul(eq, next)
}
return
}
121 changes: 121 additions & 0 deletions std/polynomial/polynomial_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
package polynomial

import (
"github.com/consensys/gnark-crypto/ecc"
"github.com/consensys/gnark/frontend"
"github.com/consensys/gnark/test"
"testing"
)

type evalPolyCircuit struct {
P []frontend.Variable `gnark:",public"`
At frontend.Variable `gnark:",secret"`
Evaluation frontend.Variable `gnark:",secret"`
}

func (c *evalPolyCircuit) Define(api frontend.API) error {
p := Polynomial(c.P)
evaluation := p.Eval(api, c.At)
api.AssertIsEqual(evaluation, c.Evaluation)
return nil
}

func TestEvalPoly(t *testing.T) {
assert := test.NewAssert(t)

witness := evalPolyCircuit{
P: Polynomial{1, 2, 3, 4},
At: 5,
Evaluation: 586,
}

assert.SolvingSucceeded(&evalPolyCircuit{P: make(Polynomial, 4)}, &witness, test.WithCurves(ecc.BN254))
}

type evalMultiLinCircuit struct {
M []frontend.Variable `gnark:",public"`
At []frontend.Variable `gnark:",secret"`
Evaluation frontend.Variable `gnark:",secret"`
}

func (c *evalMultiLinCircuit) Define(api frontend.API) error {
m := MultiLin(c.M)
evaluation := m.Eval(api, c.At)
api.AssertIsEqual(evaluation, c.Evaluation)
return nil
}

func TestEvalMultiLin(t *testing.T) {
assert := test.NewAssert(t)

// M = 2 X_0 + X_1 + 1
witness := evalMultiLinCircuit{
M: MultiLin{1, 2, 3, 4},
At: []frontend.Variable{5, 6},
Evaluation: 17,
}

assert.SolvingSucceeded(&evalMultiLinCircuit{M: make(MultiLin, 4), At: make([]frontend.Variable, 2)}, &witness, test.WithCurves(ecc.BN254))
}

type evalEqCircuit struct {
X []frontend.Variable `gnark:",public"`
Y []frontend.Variable `gnark:",secret"`
Eq frontend.Variable `gnark:"secret"`
}

func (c *evalEqCircuit) Define(api frontend.API) error {
evaluation := EvalEq(api, c.X, c.Y)
api.AssertIsEqual(evaluation, c.Eq)
return nil
}

func TestEvalEq(t *testing.T) {
assert := test.NewAssert(t)

witness := evalEqCircuit{
X: []frontend.Variable{1, 2, 3, 4},
Y: []frontend.Variable{5, 6, 7, 8},
Eq: 148665,
}

assert.SolvingSucceeded(&evalEqCircuit{X: make([]frontend.Variable, 4), Y: make([]frontend.Variable, 4)}, &witness, test.WithCurves(ecc.BN254))
}

type interpolateLDEOnRangeCircuit struct {
At frontend.Variable `gnark:",secret"`
Values []frontend.Variable `gnark:",public"`
InterpolatedValue frontend.Variable `gnark:",secret"`
}

func (c *interpolateLDEOnRangeCircuit) Define(api frontend.API) error {
evaluation := InterpolateLDEOnRange(api, c.At, c.Values)
api.AssertIsEqual(evaluation, c.InterpolatedValue)
return nil
}

func TestInterpolateLDEOnRange(t *testing.T) {
assert := test.NewAssert(t)

// The polynomial is 2 X^4 - X^3 - 9 X^2 + 9 X - 6
witness := interpolateLDEOnRangeCircuit{
At: 5,
Values: []frontend.Variable{-6, -5, 0, 75, 334},
InterpolatedValue: 939,
}

assert.SolvingSucceeded(&interpolateLDEOnRangeCircuit{Values: make([]frontend.Variable, 5)}, &witness, test.WithCurves(ecc.BN254))
}

func TestInterpolateLDEOnRangeWithinRange(t *testing.T) {
assert := test.NewAssert(t)

// The polynomial is 2 X^4 - X^3 - 9 X^2 + 9 X - 6
witness := interpolateLDEOnRangeCircuit{
At: 1,
Values: []frontend.Variable{-6, -5, 0, 75, 334},
InterpolatedValue: -5,
}

assert.SolvingSucceeded(&interpolateLDEOnRangeCircuit{Values: make([]frontend.Variable, 5)}, &witness)
}

0 comments on commit 21aca78

Please sign in to comment.