-
Notifications
You must be signed in to change notification settings - Fork 335
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #361 from ConsenSys/feat/polynomial
Feat/polynomial
- Loading branch information
Showing
3 changed files
with
238 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} |