From f0552b3faefd948c6692017c956e8ed8b1b1a4a4 Mon Sep 17 00:00:00 2001 From: nikkolasg Date: Wed, 18 Nov 2020 18:39:47 +0000 Subject: [PATCH 1/4] pedersen test --- go.sum | 1 + pedersen/commit.go | 47 +++++++++++++++++++++++++++++++++++++++++ pedersen/commit_test.go | 27 +++++++++++++++++++++++ 3 files changed, 75 insertions(+) create mode 100644 pedersen/commit.go create mode 100644 pedersen/commit_test.go diff --git a/go.sum b/go.sum index f523259..892b0bb 100644 --- a/go.sum +++ b/go.sum @@ -27,6 +27,7 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +go.dedis.ch/fixbuf v1.0.3 h1:hGcV9Cd/znUxlusJ64eAlExS+5cJDIyTyEG+otu5wQs= go.dedis.ch/fixbuf v1.0.3/go.mod h1:yzJMt34Wa5xD37V5RTdmp38cz3QhMagdGoem9anUalw= go.dedis.ch/kyber/v3 v3.0.4/go.mod h1:OzvaEnPvKlyrWyp3kGXlFdp7ap1VC6RkZDTaPikqhsQ= go.dedis.ch/kyber/v3 v3.0.9 h1:i0ZbOQocHUjfFasBiUql5zVeC7u/vahFd96DFA8UOWk= diff --git a/pedersen/commit.go b/pedersen/commit.go new file mode 100644 index 0000000..6f70928 --- /dev/null +++ b/pedersen/commit.go @@ -0,0 +1,47 @@ +package pedersen + +import ( + "github.com/drand/kyber" + "github.com/drand/kyber/util/random" +) + +// Group has points on it and can create scalar from the scalar fields +type Group = kyber.Group + +// Scalar of the field of the curve +type Scalar = kyber.Scalar + +// Point in the group (in our case it's elliptic curve so it's a point) +type Point = kyber.Point + +// Setup contains the setup information for pedersen commitments +type Setup struct { + gs []Point + h Point + g Group +} + +// NewSetup returns the setup necessary to compute a commitment +func NewSetup(g Group, l int) Setup { + gs := make([]Point, l) + for i := 0; i < l; i++ { + gs[i] = g.Point().Pick(random.New()) + } + h := g.Point().Pick(random.New()) + return Setup{ + gs: gs, + h: h, + g: g, + } +} + +// Commit compute the commitment of the points given the setup and the +// randomness to use. +func Commit(s Setup, msgs []Scalar, r Scalar) Point { + var acc = s.g.Point().Mul(r, s.h) + for i, m := range msgs { + gxi := s.g.Point().Mul(m, s.gs[i]) + acc.Add(acc, gxi) + } + return acc +} diff --git a/pedersen/commit_test.go b/pedersen/commit_test.go new file mode 100644 index 0000000..98ead76 --- /dev/null +++ b/pedersen/commit_test.go @@ -0,0 +1,27 @@ +package pedersen + +import ( + "testing" + + "github.com/drand/kyber/group/edwards25519" + "github.com/drand/kyber/util/random" + "github.com/stretchr/testify/require" +) + +func TestCommit(t *testing.T) { + var g = edwards25519.NewBlakeSHA256Ed25519() + var l = 10 + setup := NewSetup(g, l) + msgs := make([]Scalar, l-1) + for i := range msgs { + msgs[i] = g.Scalar().Pick(random.New()) + } + + r := g.Scalar().Pick(random.New()) + c1 := Commit(setup, msgs, r) + c2 := Commit(setup, msgs, r) + require.True(t, c1.Equal(c2)) + r2 := g.Scalar().Pick(random.New()) + c3 := Commit(setup, msgs, r2) + require.False(t, c1.Equal(c3)) +} From 2140c199ee5323b16d31445315202ffbd705f9b1 Mon Sep 17 00:00:00 2001 From: nikkolasg Date: Thu, 19 Nov 2020 14:53:15 +0000 Subject: [PATCH 2/4] polynomial factored out --- algebra.go | 288 ------------------------ algebra_test.go | 187 ++------------- groth16.go | 3 +- ipa.go | 7 + pedersen/{ => commit}/commit.go | 29 ++- pedersen/{ => commit}/commit_test.go | 2 +- pedersen/poly.go | 107 +++++++++ pinocchio_test.go | 5 +- pinochio.go | 5 +- polynomial/poly.go | 325 +++++++++++++++++++++++++++ polynomial/poly_test.go | 157 +++++++++++++ qap.go | 43 ++-- 12 files changed, 665 insertions(+), 493 deletions(-) create mode 100644 ipa.go rename pedersen/{ => commit}/commit.go (61%) rename pedersen/{ => commit}/commit_test.go (97%) create mode 100644 pedersen/poly.go create mode 100644 polynomial/poly.go create mode 100644 polynomial/poly_test.go diff --git a/algebra.go b/algebra.go index ef1da1d..24e92cd 100644 --- a/algebra.go +++ b/algebra.go @@ -1,12 +1,5 @@ package playsnark -import ( - "fmt" - "sort" - - "github.com/drand/kyber" -) - type Value int type Vector []Value @@ -71,287 +64,6 @@ func (v Vector) IsZero() bool { return true } -type Poly []Element -type PolyCommit []Commit - -func (p Poly) Mul(p2 Poly) Poly { - l := len(p) + len(p2) - 1 - output := make(Poly, l) - for i := 0; i < l; i++ { - output[i] = NewElement() - } - for i, v1 := range p { - for j, v2 := range p2 { - tmp := NewElement().Mul(v1, v2) - output[i+j] = output[i+j].Add(output[i+j], tmp) - } - } - return output -} - -func (p Poly) Eval(i Element) Element { - xi := i.Clone() - v := zero.Clone() - for j := len(p) - 1; j >= 0; j-- { - v.Mul(v, xi) - v.Add(v, p[j]) - } - return v -} - -// Div returns the quotient p / p2 and the remainder using polynomial synthetic -// division -func (p Poly) Div(p2 Poly) (q Poly, r Poly) { - dividend := p - divisor := p2 - out := make(Poly, len(dividend)) - for i, c := range dividend { - out[i] = NewElement().Set(c) - } - for i := 0; i < len(dividend)-(len(divisor)-1); i++ { - out[i].Div(out[i], divisor[0]) - if coef := out[i]; !coef.Equal(zero) { - var a = NewElement() - for j := 1; j < len(divisor); j++ { - out[i+j].Add(out[i+j], a.Mul(a.Neg(divisor[j]), coef)) - } - } - } - separator := len(out) - (len(divisor) - 1) - return out[:separator], out[separator:] -} - -// Long polynomial division -func (p *Poly) Div2(p2 Poly) (q Poly, r Poly) { - r = p.Clone() - for len(r) > 0 && len(r) >= len(p2) { - num := r[len(r)-1].Clone() - t := num.Div(num, p2[len(p2)-1]) - degreeT := len(r) - len(p2) - tPoly := newPoly(degreeT) - tPoly[len(tPoly)-1] = t - q = q.Add(tPoly) - // tPoly is n-th coefficient of p / highest of p2 (degree m) - // (a / b) * x^(n-m) - // so tPoly * p2 has degree n - // tPoly * p2 = a * x^n + ... x^n-1 + ... - // so we can remove the last coefficient of "r" since r always contains - // the highest coefficient of p not "removed" so far - r = r.Sub(tPoly.Mul(p2))[:len(r)-1] - } - r.Normalize() - return -} - -func (p Poly) Add(p2 Poly) Poly { - max := len(p) - if max < len(p2) { - max = len(p2) - } - - output := make(Poly, max) - for i := range p { - output[i] = NewElement().Set(p[i]) - } - for i := range p2 { - if output[i] == nil { - output[i] = NewElement() - } - output[i] = output[i].Add(output[i], p2[i]) - } - return output -} - -func (p Poly) Sub(p2 Poly) Poly { - max := len(p) - if max < len(p2) { - max = len(p2) - } - - output := make(Poly, max) - for i := range p { - output[i] = NewElement().Set(p[i]) - } - for i := range p2 { - if output[i] == nil { - output[i] = NewElement() - } - output[i] = output[i].Sub(output[i], p2[i]) - } - return output -} -func (p Poly) Equal(p2 Poly) bool { - if len(p) != len(p) { - return false - } - - for i := 0; i < len(p); i++ { - if !p[i].Equal(p2[i]) { - return false - } - } - return true -} - -func (p Poly) Clone() Poly { - o := make(Poly, len(p)) - for i := 0; i < len(p); i++ { - o[i] = p[i].Clone() - } - return o -} - -func newPoly(d int) Poly { - o := make(Poly, d+1) - for i := 0; i <= d; i++ { - o[i] = NewElement() - } - return o -} - -// Normalize remove all the 0 coefficients from the highest degree downwards -// until it encounters a non zero coefficients (i.e. len(p) will give the degree -// of the coefficient) -func (p Poly) Normalize() Poly { - maxi := len(p) - for i := len(p) - 1; i >= 0; i-- { - if !p[i].Equal(zero) { - return p[:maxi] - } - maxi-- - } - return p[:maxi] -} - -func (p Poly) Degree() int { - return len(p) - 1 -} - -type pair struct { - I int - V Element -} - -// Interpolate takes a list of element [ y_1, ... y_n] and returns -// a polynomial p such that (note the indices) -// p(1) = y_1, p(2) = y_2, ... p(n) = y_n -// Code largely taken from github.com/drand/kyber -func Interpolate(ys []Element) Poly { - var pairs []pair - for i, y := range ys { - pairs = append(pairs, pair{I: i + 1, V: y}) - } - x, y := xyScalar(Group, pairs) - - var accPoly = Poly([]Element{zero}) - //den := g.Scalar() - // Notations follow the Wikipedia article on Lagrange interpolation - // https://en.wikipedia.org/wiki/Lagrange_polynomial - for j := range x { - basis := lagrangeBasis(Group, j, x) - for i := range basis { - basis[i] = basis[i].Mul(basis[i], y[j]) - } - - if accPoly == nil { - accPoly = basis - continue - } - - // add all L_j * y_j together - accPoly = accPoly.Add(basis) - } - return accPoly -} - -type byIndexScalar []pair - -func (s byIndexScalar) Len() int { return len(s) } -func (s byIndexScalar) Swap(i, j int) { s[i], s[j] = s[j], s[i] } -func (s byIndexScalar) Less(i, j int) bool { return s[i].I < s[j].I } - -// xyScalar returns the list of (x_i, y_i) pairs indexed. The first map returned -// is the list of x_i and the second map is the list of y_i, both indexed in -// their respective map at index i. -func xyScalar(g kyber.Group, shares []pair) (map[int]Element, map[int]Element) { - // we are sorting first the shares since the shares may be unrelated for - // some applications. In this case, all participants needs to interpolate on - // the exact same order shares. - sorted := make([]pair, 0, len(shares)) - for _, share := range shares { - sorted = append(sorted, share) - } - sort.Sort(byIndexScalar(sorted)) - - x := make(map[int]kyber.Scalar) - y := make(map[int]kyber.Scalar) - for _, s := range sorted { - if s.V == nil || s.I < 0 { - continue - } - idx := s.I - x[idx] = g.Scalar().SetInt64(int64(idx)) - y[idx] = s.V - } - return x, y -} - -// lagrangeBasis returns a PriPoly containing the Lagrange coefficients for the -// i-th position. xs is a mapping between the indices and the values that the -// interpolation is using, computed with xyScalar(). -func lagrangeBasis(g kyber.Group, i int, xs map[int]Element) Poly { - var basis = Poly([]Element{one.Clone()}) - // compute lagrange basis l_j - den := g.Scalar().One() - var acc = g.Scalar().One() - for m, xm := range xs { - if i == m { - continue - } - // multiply by x -i - basis = basis.Mul(Poly([]Element{NewElement().Neg(xm), one.Clone()})) - den.Sub(xs[i], xm) // den = xi - xm - den.Inv(den) // den = 1 / den - acc.Mul(acc, den) // acc = acc * den - } - - // multiply all coefficients by the denominator - for i := range basis { - basis[i] = basis[i].Mul(basis[i], acc) - } - return basis -} - -// blindEvaluation takes a polynomial p(x) and a list of blinded points {s} -// such that the i-th value in blindedPoint is equal to s^i, s being unknown -// from the trusted setup. -// the result is SUM( g^(s^i)^p[i] ) <=> (in addition form) SUM(p[i] * (s^i * g) -// which is equivalent to g^p(s) -// We blindly evaluate for all coefficients of p, blindedPoints can be of higher -// degree it doesn't affect the result, but it must have at least the same -// degree as p -func (p Poly) BlindEval(zero Commit, blindedPoint []Commit) Commit { - // XXX change that to equality - if len(p) != len(blindedPoint) { - panic(fmt.Sprintf("mismatch of length between poly %d and blinded eval points %d", len(p), len(blindedPoint))) - } - var acc = zero.Clone() - var tmp = zero.Clone() - for i := 0; i < len(p); i++ { - acc = acc.Add(acc, tmp.Mul(p[i], blindedPoint[i])) - } - return acc -} - -func (p Poly) Commit(base Commit) PolyCommit { - var pp = make([]Commit, 0, len(p)) - for _, c := range p { - bb := base.Clone() - pp = append(pp, bb.Mul(c, bb)) - } - return pp -} - // GeneratePowersCommit returns { g^shift * s^i} for i=0...power included func GeneratePowersCommit(base Commit, e Element, shift Element, power int) []Commit { var gi = make([]Commit, 0, power+1) diff --git a/algebra_test.go b/algebra_test.go index f637ebf..768a035 100644 --- a/algebra_test.go +++ b/algebra_test.go @@ -4,135 +4,10 @@ import ( "testing" "github.com/drand/kyber/util/random" + poly "github.com/nikkolasg/playsnark/polynomial" "github.com/stretchr/testify/require" ) -func TestAlgebraEval(t *testing.T) { - var p Poly = Poly([]Element{ - Value(1).ToFieldElement(), - Value(1).ToFieldElement(), - }) - - res := p.Eval(Value(1).ToFieldElement()) - exp := Value(2).ToFieldElement() - require.True(t, res.Equal(exp)) -} - -func TestAlgebraBlindEval(t *testing.T) { - var d = 4 - var p = randomPoly(4) - var x = NewElement().Pick(random.New()) - var px = p.Eval(x) - var shift = NewElement().Pick(random.New()) - // g^p(x) - var gpx = NewG1().Mul(px, nil) - // g^(p(x) * shift) - var shiftGpx = NewG1().Mul(shift, gpx) - - var blindedPoints = GeneratePowersCommit(zeroG1, x, shift, d) - var res = p.BlindEval(zeroG1, blindedPoints) - require.True(t, shiftGpx.Equal(res)) -} - -func TestAlgebraInterpolate(t *testing.T) { - var r = randomPoly(4) - p := Interpolate([]Element(r)) - for i := 0; i < len(r); i++ { - res := p.Eval(Value(i + 1).ToFieldElement()) - exp := r[i] - require.True(t, res.Equal(exp)) - } -} - -// Test the minimal polynomial division theorem in algebra -func TestAlgebraMinimal(t *testing.T) { - // t(x) = h(x) * z(x) - // where - // z(x) is the minimal polynomial - // <=> z(x) = (x-1) * (x-2) - // h(x) = 2x - // therefore - // t(x) = (x-1)*(x-2)*2x = 2x^3 - 6x^2 + 4x - var p Poly = Poly([]Element{ - zero.Clone(), - Value(4).ToFieldElement(), - NewElement().Neg(Value(6).ToFieldElement()), - Value(2).ToFieldElement(), - }) - - var z Poly = Poly([]Element{one.Clone()}) - for i := 1; i <= 2; i++ { - root := Poly([]Element{ - NewElement().Neg(Value(i).ToFieldElement()), - one.Clone(), - }) - z = z.Mul(root) - } - // p / z should give h(x) without any remainder - _, rem := p.Div2(z) - require.True(t, len(rem.Normalize()) == 0) -} - -func TestAlgebraPolyMul(t *testing.T) { - // p1(x) = 1 + 2x - var p1 Poly = []Element{ - Value(1).ToFieldElement(), - Value(2).ToFieldElement(), - } - // p2(x) = 3 + x^2 - var p2 Poly = []Element{ - Value(3).ToFieldElement(), - NewElement(), - Value(1).ToFieldElement(), - } - - // p3(x) = (1 + 2x) * (3 + x^2) - // = 3 + 6x + x^2 + 2x^3 - var exp Poly = []Element{ - Value(3).ToFieldElement(), - Value(6).ToFieldElement(), - Value(1).ToFieldElement(), - Value(2).ToFieldElement(), - } - - p3 := p1.Mul(p2) - - require.Equal(t, len(exp), len(p3)) - for i := 0; i < len(p3); i++ { - require.Equal(t, p3[i], exp[i]) - } -} - -func TestAlgebraPolyAdd(t *testing.T) { - type TestVector struct { - A Poly - B Poly - } - for row, tv := range []TestVector{ - { - A: randomPoly(4), - B: randomPoly(6), - }, - { - A: randomPoly(6), - B: randomPoly(4), - }, - { - A: randomPoly(5), - B: randomPoly(5), - }, - } { - x := 10 - res := tv.A.Add(tv.B) - // test for homomorphism - // A(x) + B(x) = (A + B)(x) - xf := Value(x).ToFieldElement() - exp := tv.A.Eval(xf) - exp = exp.Add(exp, tv.B.Eval(xf)) - require.Equal(t, res.Eval(xf), exp, "failed at row %d", row) - } -} - func TestAlgebraMatrixTranspose(t *testing.T) { var m Matrix = Matrix([]Vector{ Vector([]Value{Value(1), Value(2), Value(3), Value(4)}), @@ -152,52 +27,18 @@ func TestAlgebraMatrixTranspose(t *testing.T) { require.Equal(t, exp, tm) } -func TestAlgebraPolyDivManual(t *testing.T) { - // p2(x) = 1 + 2x - p2 := Poly([]Element{ - Value(1).ToFieldElement(), - Value(2).ToFieldElement(), - }) - // q = 2x - q := Poly([]Element{ - NewElement(), - Value(2).ToFieldElement(), - }) - // p1 = p2 * q = (1+2x) * 2x = 2x + 4x^2 - p1 := Poly([]Element{ - NewElement(), - Value(2).ToFieldElement(), - Value(4).ToFieldElement(), - }) - - expQ, expR := p1.Div(p2) - require.True(t, q.Equal(expQ)) - require.Equal(t, len(expR.Normalize()), 0) -} - -func TestAlgebraPolyDiv(t *testing.T) { - p1 := randomPoly(5) - p2 := randomPoly(4) - // p1 = q * p2 + r - q, r := p1.Div2(p2) - - // so p2 * q + r <=> p1 - exp := q.Mul(p2).Add(r) - require.True(t, p1.Equal(exp)) - - h := Poly([]Element{Value(2).ToFieldElement(), Value(2).ToFieldElement()}) - // hp1 = p1 * h - hp1 := p1.Mul(h) - // q = hp1 / h = p1 - exp1, rem := hp1.Div2(h) - require.True(t, exp1.Equal(p1)) - require.True(t, len(rem.Normalize()) == 0) -} +func TestAlgebraBlindEval(t *testing.T) { + var d = 4 + var p = poly.RandomPoly(Group, 4) + var x = NewElement().Pick(random.New()) + var px = p.Eval(x) + var shift = NewElement().Pick(random.New()) + // g^p(x) + var gpx = Group.Point().Mul(px, nil) + // g^(p(x) * shift) + var shiftGpx = Group.Point().Mul(shift, gpx) -func randomPoly(d int) Poly { - var poly = make(Poly, 0, d+1) - for i := 0; i <= d; i++ { - poly = append(poly, NewElement().Pick(random.New())) - } - return poly + var blindedPoints = GeneratePowersCommit(zeroG1, x, shift, d) + var res = p.BlindEval(Group.Point().Base(), blindedPoints) + require.True(t, shiftGpx.Equal(res)) } diff --git a/groth16.go b/groth16.go index 52ca3fe..c0b839b 100644 --- a/groth16.go +++ b/groth16.go @@ -2,6 +2,7 @@ package playsnark import ( "github.com/drand/kyber/util/random" + poly "github.com/nikkolasg/playsnark/polynomial" ) // Implements Groth16 paper https://eprint.iacr.org/2016/260.pdf @@ -131,7 +132,7 @@ func Groth16Prove(tr Groth16Setup, q QAP, sol Vector) Groth16Proof { // g^u_i(x) = SUM_j( g^(x^i)^coeff(u_i,j)) = g^(u0 *x^0 + u1*x^1 + ...) // We then multiply by the solution vector to have g^(s_i * u_i(x)) // and iteratively sum the results for all variables - sumBlind := func(basis Commit, polys []Poly, xi []Commit) Commit { + sumBlind := func(basis Commit, polys []poly.Poly, xi []Commit) Commit { var sum = basis.Clone().Null() for i := 0; i < q.nbVars; i++ { uix := polys[i].BlindEval(basis.Clone().Null(), xi) diff --git a/ipa.go b/ipa.go new file mode 100644 index 0000000..1283e5d --- /dev/null +++ b/ipa.go @@ -0,0 +1,7 @@ +package playsnark + +// Implements the Inner Product Argument from bulletproof +// paper https://eprint.iacr.org/2017/1066.pdf +// +// Goal is to prove, from input g,h \in G^n, scalar c, and P and G \in G that +// prover knows a and b \in Z^n such that P = g^a * h^b and c = < a b > diff --git a/pedersen/commit.go b/pedersen/commit/commit.go similarity index 61% rename from pedersen/commit.go rename to pedersen/commit/commit.go index 6f70928..0d04c7f 100644 --- a/pedersen/commit.go +++ b/pedersen/commit/commit.go @@ -1,6 +1,11 @@ -package pedersen +package commit import ( + "bytes" + "crypto/cipher" + "crypto/sha256" + "encoding/binary" + "github.com/drand/kyber" "github.com/drand/kyber/util/random" ) @@ -17,7 +22,7 @@ type Point = kyber.Point // Setup contains the setup information for pedersen commitments type Setup struct { gs []Point - h Point + s Point g Group } @@ -25,20 +30,32 @@ type Setup struct { func NewSetup(g Group, l int) Setup { gs := make([]Point, l) for i := 0; i < l; i++ { - gs[i] = g.Point().Pick(random.New()) + gs[i] = g.Point().Pick(oracle(g, l, i)) } - h := g.Point().Pick(random.New()) + s := g.Point().Pick(oracle(g, l, l+1)) return Setup{ gs: gs, - h: h, + s: s, g: g, } } +// returns an oracle for deriving a point for pedersen commitment +func oracle(g Group, l, pos int) cipher.Stream { + var h = sha256.New() + h.Write([]byte("pedersen-commit")) + h.Write([]byte(g.String())) + binary.Write(h, binary.LittleEndian, l) + binary.Write(h, binary.LittleEndian, pos) + var b bytes.Buffer + b.Write(h.Sum(nil)) + return random.New(&b) +} + // Commit compute the commitment of the points given the setup and the // randomness to use. func Commit(s Setup, msgs []Scalar, r Scalar) Point { - var acc = s.g.Point().Mul(r, s.h) + var acc = s.g.Point().Mul(r, s.s) for i, m := range msgs { gxi := s.g.Point().Mul(m, s.gs[i]) acc.Add(acc, gxi) diff --git a/pedersen/commit_test.go b/pedersen/commit/commit_test.go similarity index 97% rename from pedersen/commit_test.go rename to pedersen/commit/commit_test.go index 98ead76..036e79c 100644 --- a/pedersen/commit_test.go +++ b/pedersen/commit/commit_test.go @@ -1,4 +1,4 @@ -package pedersen +package commit import ( "testing" diff --git a/pedersen/poly.go b/pedersen/poly.go new file mode 100644 index 0000000..8132a1b --- /dev/null +++ b/pedersen/poly.go @@ -0,0 +1,107 @@ +package pedersen + +import ( + "bytes" + "crypto/cipher" + "crypto/sha256" + "encoding/binary" + + "github.com/drand/kyber" + "github.com/drand/kyber/util/random" + "github.com/nikkolasg/playsnark/pedersen/commit" +) + +// Group has points on it and can create scalar from the scalar fields +type Group = kyber.Group + +// Scalar of the field of the curve +type Scalar = kyber.Scalar + +// Point in the group (in our case it's elliptic curve so it's a point) +type Point = kyber.Point + +type Setup struct { + commit.Setup + h Point + degree int +} + +// NewSetup returns the information necessary for using the pedersen polynomail +// commitment +func NewSetup(g Group, degree int) Setup { + s := commit.NewSetup(g, degree+1) + h := g.Point().Pick(oracle(g, degree)) + return Setup{ + Setup: s, + h: h, + degree: degree, + } +} + +type Polynomial = []Scalar + +type Commitment = Point + +// Commit the pedersen commitment of the coefficients +func Commit(s Setup, p Polynomial, r Scalar) Commitment { + return commit.Commit(s.Setup, p, r) +} + +func Open(s Setup, p Polynomial, c Commitment, z, r Scalar) Proof { + // v = p(z) + v := evalPoly(s.g, p, eval) + // random poly p2 such that p2(z) = 0 + // naive strategy: create a random one, then eval p2(z) = a then substract + // a from the free coefficient to p2' such that p2'(z) = p2(z) - a = 0 + p2 := randomPoly(s.g, s.degree) + a := evalPoly(s.g, p2, z) + p2[0] = p2[0].Sub(p2[0], a) + // sample randomness w + w := s.g.Scalar().Pick(random.New()) + // commit to p2 using w + c2 := commit.Commit(s.Setup, p2, w) + // compute challenge from random oracle + alpha := oracleFromScalars(c, z, v, c2) + +} + +func evalPoly(g Group, p Polynomial, x Scalar) Scalar { + xi := x.Clone() + v := g.Scalar().Zero() + for j := len(p) - 1; j >= 0; j-- { + v.Mul(v, xi) + v.Add(v, p[j]) + } + return v +} + +func randomPoly(g Group, degree int) Polynomial { + p := make(Polynomial, degree+1) + for i := 0; i <= degree; i++ { + p[i] = g.Scalar().Pick(random.New()) + } + return p +} + +// returns an oracle for deriving a point for pedersen polynomial commitment +func oracle(g Group, degree int) cipher.Stream { + var h = sha256.New() + h.Write([]byte("pedersen-polycommit")) + h.Write([]byte(g.String())) + binary.Write(h, binary.LittleEndian, degree) + var b bytes.Buffer + b.Write(h.Sum(nil)) + return random.New(&b) +} + +func oracleFromScalar(g Group, scalars ...Scalar) cipher.Stream { + var h = sha256.New() + h.Write([]byte("pedersen-polycommit-scalar")) + h.Write([]byte(g.String())) + for _, s := range scalars { + s.WriteTo(h) + } + var b bytes.Buffer + b.Write(h.Sum(nil)) + return random.New(&b) +} diff --git a/pinocchio_test.go b/pinocchio_test.go index 048858c..4625210 100644 --- a/pinocchio_test.go +++ b/pinocchio_test.go @@ -5,12 +5,13 @@ import ( "testing" "github.com/drand/kyber/util/random" + poly "github.com/nikkolasg/playsnark/polynomial" "github.com/stretchr/testify/require" ) func TestPinocchioCombine(t *testing.T) { var d = 4 - var p = randomPoly(4) + var p = poly.RandomPoly(Group, 4) var x = NewElement().Pick(random.New()) var px = p.Eval(x) var gpx = NewG1().Mul(px, nil) @@ -35,7 +36,7 @@ func TestPinocchioProofValidDivision(t *testing.T) { px := left.Mul(right).Sub(out) // h(x) = p(x) / t(x) hx, rem := px.Div2(qap.z) - if len(rem.Normalize()) > 0 { + if len(rem.Normalize().Coeffs()) > 0 { panic("apocalypse") } hs := hx.Eval(setup.t.s) diff --git a/pinochio.go b/pinochio.go index 853423e..a5a032d 100644 --- a/pinochio.go +++ b/pinochio.go @@ -5,6 +5,7 @@ import ( "fmt" "github.com/drand/kyber/util/random" + poly "github.com/nikkolasg/playsnark/polynomial" ) // This file implements the logic of the Pinocchio PHGR13 proof system mostly as @@ -211,7 +212,7 @@ func PHGR13Prove(ek PHGR13EvalKey, qap QAP, solution Vector) PHGR13Proof { px := left.Mul(right).Sub(out) // h(x) = p(x) / t(x) hx, rem := px.Div2(qap.z) - if len(rem.Normalize()) > 0 { + if len(rem.Normalize().Coeffs()) > 0 { panic("apocalypse") } // g^h(s) = SUM(s_i * G) @@ -378,7 +379,7 @@ func PHGR13Verify(vk PHGR13VerifKey, qap QAP, p PHGR13Proof, io Vector) bool { // commitments using the base and shifted by "shift". Note by giving shift as // one, nothing happens ! // { g^(shift * p_i(x)) } for all p_i given -func generateEvalCommit(base Commit, polys []Poly, x, shift Element) []Commit { +func generateEvalCommit(base Commit, polys []poly.Poly, x, shift Element) []Commit { var ret = make([]Commit, 0, len(polys)) for i := range polys { tmp := polys[i].Normalize().Eval(x) diff --git a/polynomial/poly.go b/polynomial/poly.go new file mode 100644 index 0000000..bb2a129 --- /dev/null +++ b/polynomial/poly.go @@ -0,0 +1,325 @@ +package poly + +import ( + "fmt" + "sort" + + "github.com/drand/kyber" + "go.dedis.ch/kyber/v3/util/random" +) + +// Group has points on it and can create scalar from the scalar fields +type Group = kyber.Group + +// Scalar of the field of the curve +type Scalar = kyber.Scalar + +// Point in the group (in our case it's elliptic curve so it's a point) +type Point = kyber.Point + +type Poly struct { + c []Scalar + g Group +} + +type PolyCommit []Point + +func emptyPoly(g Group, d int) Poly { + o := make([]Scalar, d+1) + for i := 0; i <= d; i++ { + o[i] = g.Scalar() + } + return Poly{c: o, g: g} +} + +func NewZeroPoly(g Group) Poly { + return Poly{c: []Scalar{}, g: g} +} + +func NewPolyFrom(g Group, coeffs []Scalar) Poly { + return Poly{c: coeffs, g: g} +} + +func (p Poly) Mul(p2 Poly) Poly { + l := len(p.c) + len(p2.c) - 1 + output := make([]Scalar, l) + for i := 0; i < l; i++ { + output[i] = p.g.Scalar().Zero() + } + for i, v1 := range p.c { + for j, v2 := range p2.c { + tmp := p.g.Scalar().Mul(v1, v2) + output[i+j] = output[i+j].Add(output[i+j], tmp) + } + } + return Poly{c: output, g: p.g} +} + +func (p Poly) Eval(i Scalar) Scalar { + xi := i.Clone() + v := p.g.Scalar().Zero() + for j := len(p.c) - 1; j >= 0; j-- { + v.Mul(v, xi) + v.Add(v, p.c[j]) + } + return v +} + +// Div returns the quotient p / p2 and the remainder using polynomial synthetic +// division +func (p Poly) Div(p2 Poly) (q Poly, r Poly) { + dividend := p.c + divisor := p2.c + out := make([]Scalar, len(dividend)) + for i, c := range dividend { + out[i] = p.g.Scalar().Set(c) + } + for i := 0; i < len(dividend)-(len(divisor)-1); i++ { + out[i].Div(out[i], divisor[0]) + if coef := out[i]; !coef.Equal(p.g.Scalar().Zero()) { + var a = p.g.Scalar() + for j := 1; j < len(divisor); j++ { + out[i+j].Add(out[i+j], a.Mul(a.Neg(divisor[j]), coef)) + } + } + } + separator := len(out) - (len(divisor) - 1) + return Poly{c: out[:separator], g: p.g}, Poly{c: out[separator:], g: p.g} +} + +// Long polynomial division +func (p *Poly) Div2(p2 Poly) (q Poly, r Poly) { + r = p.Clone() + q = NewPolyFrom(p.g, nil) + for len(r.c) > 0 && len(r.c) >= len(p2.c) { + num := r.c[len(r.c)-1].Clone() + t := num.Div(num, p2.c[len(p2.c)-1]) + degreeT := len(r.c) - len(p2.c) + tPoly := emptyPoly(p.g, degreeT) + tPoly.c[len(tPoly.c)-1] = t + q = q.Add(tPoly) + // tPoly is n-th coefficient of p / highest of p2 (degree m) + // (a / b) * x^(n-m) + // so tPoly * p2 has degree n + // tPoly * p2 = a * x^n + ... x^n-1 + ... + // so we can remove the last coefficient of "r" since r always contains + // the highest coefficient of p not "removed" so far + r = NewPolyFrom(p.g, r.Sub(tPoly.Mul(p2)).c[:len(r.c)-1]) + } + r.Normalize() + return +} + +func (p Poly) Add(p2 Poly) Poly { + max := len(p.c) + if max < len(p2.c) { + max = len(p2.c) + } + + output := make([]Scalar, max) + for i := range p.c { + output[i] = p.g.Scalar().Set(p.c[i]) + } + for i := range p2.c { + if output[i] == nil { + output[i] = p.g.Scalar() + } + output[i] = output[i].Add(output[i], p2.c[i]) + } + return Poly{c: output, g: p.g} +} + +func (p Poly) Sub(p2 Poly) Poly { + max := len(p.c) + if max < len(p2.c) { + max = len(p2.c) + } + + output := make([]Scalar, max) + for i := range p.c { + output[i] = p.g.Scalar().Set(p.c[i]) + } + for i := range p2.c { + if output[i] == nil { + output[i] = p.g.Scalar() + } + output[i] = output[i].Sub(output[i], p2.c[i]) + } + return NewPolyFrom(p.g, output) +} +func (p Poly) Equal(p2 Poly) bool { + if len(p.c) != len(p.c) { + return false + } + + for i := 0; i < len(p.c); i++ { + if !p.c[i].Equal(p2.c[i]) { + return false + } + } + return true +} + +func (p Poly) Clone() Poly { + o := make([]Scalar, len(p.c)) + for i := 0; i < len(p.c); i++ { + o[i] = p.c[i].Clone() + } + return Poly{c: o, g: p.g} +} + +func (p Poly) Coeffs() []Scalar { + return p.c +} + +// Normalize remove all the 0 coefficients from the highest degree downwards +// until it encounters a non zero coefficients (i.e. len(p) will give the degree +// of the coefficient) +func (p Poly) Normalize() Poly { + maxi := len(p.c) + for i := len(p.c) - 1; i >= 0; i-- { + if !p.c[i].Equal(p.g.Scalar().Zero()) { + return NewPolyFrom(p.g, p.c[:maxi]) + } + maxi-- + } + return NewPolyFrom(p.g, p.c[:maxi]) +} + +func (p Poly) Degree() int { + return len(p.c) - 1 +} + +type pair struct { + I int + V Scalar +} + +// Interpolate takes a list of element [ y_1, ... y_n] and returns +// a polynomial p such that (note the indices) +// p(1) = y_1, p(2) = y_2, ... p(n) = y_n +// Code largely taken from github.com/drand/kyber +func Interpolate(g Group, ys []Scalar) Poly { + var pairs []pair + for i, y := range ys { + pairs = append(pairs, pair{I: i + 1, V: y}) + } + x, y := xyScalar(g, pairs) + + var accPoly Poly + var setPoly bool + //den := g.Scalar() + // Notations follow the Wikipedia article on Lagrange interpolation + // https://en.wikipedia.org/wiki/Lagrange_polynomial + for j := range x { + basis := lagrangeBasis(g, j, x) + for i := range basis.c { + basis.c[i] = basis.c[i].Mul(basis.c[i], y[j]) + } + + if !setPoly { + accPoly = basis + setPoly = true + continue + } + + // add all L_j * y_j together + accPoly = accPoly.Add(basis) + } + return accPoly +} + +type byIndexScalar []pair + +func (s byIndexScalar) Len() int { return len(s) } +func (s byIndexScalar) Swap(i, j int) { s[i], s[j] = s[j], s[i] } +func (s byIndexScalar) Less(i, j int) bool { return s[i].I < s[j].I } + +// xyScalar returns the list of (x_i, y_i) pairs indexed. The first map returned +// is the list of x_i and the second map is the list of y_i, both indexed in +// their respective map at index i. +func xyScalar(g Group, shares []pair) (map[int]Scalar, map[int]Scalar) { + // we are sorting first the shares since the shares may be unrelated for + // some applications. In this case, all participants needs to interpolate on + // the exact same order shares. + sorted := make([]pair, 0, len(shares)) + for _, share := range shares { + sorted = append(sorted, share) + } + sort.Sort(byIndexScalar(sorted)) + + x := make(map[int]Scalar) + y := make(map[int]Scalar) + for _, s := range sorted { + if s.V == nil || s.I < 0 { + continue + } + idx := s.I + x[idx] = g.Scalar().SetInt64(int64(idx)) + y[idx] = s.V + } + return x, y +} + +// lagrangeBasis returns a PriPoly containing the Lagrange coefficients for the +// i-th position. xs is a mapping between the indices and the values that the +// interpolation is using, computed with xyScalar(). +func lagrangeBasis(g Group, i int, xs map[int]Scalar) Poly { + var basis = NewPolyFrom(g, []Scalar{g.Scalar().One()}) + // compute lagrange basis l_j + den := g.Scalar().One() + var acc = g.Scalar().One() + for m, xm := range xs { + if i == m { + continue + } + // multiply by x -i + basis = basis.Mul(NewPolyFrom(g, []Scalar{g.Scalar().Neg(xm), g.Scalar().One()})) + den.Sub(xs[i], xm) // den = xi - xm + den.Inv(den) // den = 1 / den + acc.Mul(acc, den) // acc = acc * den + } + + // multiply all coefficients by the denominator + for i := range basis.c { + basis.c[i] = basis.c[i].Mul(basis.c[i], acc) + } + return basis +} + +// blindEvaluation takes a polynomial p(x) and a list of blinded points {s} +// such that the i-th value in blindedPoint is equal to s^i, s being unknown +// from the trusted setup. +// the result is SUM( g^(s^i)^p[i] ) <=> (in addition form) SUM(p[i] * (s^i * g) +// which is equivalent to g^p(s) +// We blindly evaluate for all coefficients of p, blindedPoints can be of higher +// degree it doesn't affect the result, but it must have at least the same +// degree as p +func (p Poly) BlindEval(zero Point, blindedPoint []Point) Point { + if len(p.c) != len(blindedPoint) { + panic(fmt.Sprintf("mismatch of length between poly %d and blinded eval points %d", len(p.c), len(blindedPoint))) + } + var acc = zero.Clone().Null() + var tmp = zero.Clone().Null() + for i := 0; i < len(p.c); i++ { + acc = acc.Add(acc, tmp.Mul(p.c[i], blindedPoint[i])) + } + return acc +} + +func (p Poly) Commit(base Point) PolyCommit { + var pp = make([]Point, 0, len(p.c)) + for _, c := range p.c { + bb := base.Clone() + pp = append(pp, bb.Mul(c, bb)) + } + return pp +} + +func RandomPoly(g Group, d int) Poly { + var poly = make([]Scalar, 0, d+1) + for i := 0; i <= d; i++ { + poly = append(poly, g.Scalar().Pick(random.New())) + } + return NewPolyFrom(g, poly) +} diff --git a/polynomial/poly_test.go b/polynomial/poly_test.go new file mode 100644 index 0000000..4ad115a --- /dev/null +++ b/polynomial/poly_test.go @@ -0,0 +1,157 @@ +package poly + +import ( + "testing" + + "github.com/drand/kyber/group/edwards25519" + "github.com/stretchr/testify/require" +) + +var group = edwards25519.NewBlakeSHA256Ed25519() + +func TestAlgebraEval(t *testing.T) { + var p Poly = NewPolyFrom(group, []Scalar{ + group.Scalar().SetInt64(1), + group.Scalar().SetInt64(1), + }) + + res := p.Eval(group.Scalar().SetInt64(1)) + exp := group.Scalar().SetInt64(2) + require.True(t, res.Equal(exp)) +} + +func TestAlgebraInterpolate(t *testing.T) { + var r = RandomPoly(group, 4) + p := Interpolate(group, r.c) + for i := 0; i < len(r.c); i++ { + res := p.Eval(group.Scalar().SetInt64(int64(i + 1))) + exp := r.c[i] + require.True(t, res.Equal(exp)) + } +} + +// Test the minimal polynomial division theorem in algebra +func TestAlgebraMinimal(t *testing.T) { + // t(x) = h(x) * z(x) + // where + // z(x) is the minimal polynomial + // <=> z(x) = (x-1) * (x-2) + // h(x) = 2x + // therefore + // t(x) = (x-1)*(x-2)*2x = 2x^3 - 6x^2 + 4x + var p Poly = NewPolyFrom(group, []Scalar{ + group.Scalar().Zero(), + group.Scalar().SetInt64(4), + group.Scalar().Neg(group.Scalar().SetInt64(6)), + group.Scalar().SetInt64(2), + }) + + var z Poly = NewPolyFrom(group, []Scalar{group.Scalar().One()}) + for i := 1; i <= 2; i++ { + root := NewPolyFrom(group, []Scalar{ + group.Scalar().Neg(group.Scalar().SetInt64(int64(i))), + group.Scalar().One(), + }) + z = z.Mul(root) + } + // p / z should give h(x) without any remainder + _, rem := p.Div2(z) + require.True(t, len(rem.Normalize().Coeffs()) == 0) +} + +func scalar(i int) Scalar { + return group.Scalar().SetInt64(int64(i)) +} + +func TestAlgebraPolyMul(t *testing.T) { + // p1(x) = 1 + 2x + var p1 Poly = NewPolyFrom(group, []Scalar{ + scalar(1), + scalar(2), + }) + // p2(x) = 3 + x^2 + var p2 Poly = NewPolyFrom(group, []Scalar{ + scalar(3), scalar(0), scalar(1), + }) + + // p3(x) = (1 + 2x) * (3 + x^2) + // = 3 + 6x + x^2 + 2x^3 + var exp Poly = NewPolyFrom(group, []Scalar{ + scalar(3), scalar(6), scalar(1), scalar(2), + }) + + p3 := p1.Mul(p2) + + require.Equal(t, len(exp.Coeffs()), len(p3.Coeffs())) + for i := 0; i < len(p3.c); i++ { + require.Equal(t, p3.c[i], exp.c[i]) + } +} + +func TestAlgebraPolyAdd(t *testing.T) { + type TestVector struct { + A Poly + B Poly + } + for row, tv := range []TestVector{ + { + A: RandomPoly(group, 4), + B: RandomPoly(group, 6), + }, + { + A: RandomPoly(group, 6), + B: RandomPoly(group, 4), + }, + { + A: RandomPoly(group, 5), + B: RandomPoly(group, 5), + }, + } { + x := 10 + res := tv.A.Add(tv.B) + // test for homomorphism + // A(x) + B(x) = (A + B)(x) + xf := scalar(x) + exp := tv.A.Eval(xf) + exp = exp.Add(exp, tv.B.Eval(xf)) + require.Equal(t, res.Eval(xf), exp, "failed at row %d", row) + } +} + +func TestAlgebraPolyDivManual(t *testing.T) { + // p2(x) = 1 + 2x + p2 := NewPolyFrom(group, []Scalar{ + scalar(1), scalar(2), + }) + // q = 2x + q := NewPolyFrom(group, []Scalar{ + scalar(0), scalar(2), + }) + // p1 = p2 * q = (1+2x) * 2x = 2x + 4x^2 + p1 := NewPolyFrom(group, []Scalar{ + scalar(0), scalar(2), scalar(4), + }) + + expQ, expR := p1.Div(p2) + require.True(t, q.Equal(expQ)) + require.Equal(t, len(expR.Normalize().Coeffs()), 0) +} + +func TestAlgebraPolyDiv(t *testing.T) { + p1 := RandomPoly(group, 5) + p2 := RandomPoly(group, 4) + // p1 = q * p2 + r + q, r := p1.Div2(p2) + + // so p2 * q + r <=> p1 + exp := q.Mul(p2).Add(r) + require.True(t, p1.Equal(exp)) + + h := NewPolyFrom(group, []Scalar{scalar(2), scalar(2)}) + // hp1 = p1 * h + hp1 := p1.Mul(h) + // q = hp1 / h = p1 + exp1, rem := hp1.Div2(h) + require.True(t, exp1.Equal(p1)) + require.True(t, len(rem.Normalize().Coeffs()) == 0) +} diff --git a/qap.go b/qap.go index d96e8a4..bce4732 100644 --- a/qap.go +++ b/qap.go @@ -2,6 +2,7 @@ package playsnark import ( "github.com/drand/kyber/share" + poly "github.com/nikkolasg/playsnark/polynomial" ) // QAP is the polynomial version of a R1CS circuit. It contains the left right @@ -19,11 +20,11 @@ type QAP struct { // nbGates is the variable "d" in the pinochio paper nbGates int // left right and out - left []Poly - right []Poly - out []Poly + left []poly.Poly + right []poly.Poly + out []poly.Poly // z is the minimal polynomial (x-1)(x-2)(x-3)... - z Poly + z poly.Poly } // ToQAP takes a R1CS circuit description and turns it into its polynomial QAP @@ -38,17 +39,19 @@ func ToQAP(circuit R1CS) QAP { out := qapInterpolate(circuit.out) nbVar := len(circuit.vars) nbGates := len(circuit.left) - var z Poly + var z poly.Poly + var setZ bool for i := 1; i <= nbGates; i++ { // you multiply by (x - i) with i being as high as the number of gates, // because the polynomials left,right and out vanishes on these inputs // -i + x - xi := Poly([]Element{ + xi := poly.NewPolyFrom(Group, []Element{ NewElement().Neg(Value(i).ToFieldElement()), one.Clone(), }) - if z == nil { + if !setZ { z = xi + setZ = true } else { z = z.Mul(xi) } @@ -64,7 +67,7 @@ func ToQAP(circuit R1CS) QAP { } } -func qapInterpolate(m Matrix) []Poly { +func qapInterpolate(m Matrix) []poly.Poly { // once transposed, a row of this matrix represents all the usage of this // variable at each step of the circuit // so for example, all the assignements on the lefts inputs look like this @@ -80,21 +83,21 @@ func qapInterpolate(m Matrix) []Poly { // p(1) = 1, p(2) = 0, p(3) = 1 and p(4) = 0 // We return one polynomial for each variable t := m.Transpose() - out := make([]Poly, 0, len(t)) + out := make([]poly.Poly, 0, len(t)) for _, variable := range t { var ys = make([]Element, 0, len(t)) for _, v := range variable { ys = append(ys, v.ToFieldElement()) } - poly := Interpolate(ys) + poly := poly.Interpolate(Group, ys) out = append(out, poly) } return out } -func toPoly(s *share.PriPoly) Poly { +func toPoly(s *share.PriPoly) poly.Poly { pp := s.Coefficients() - return Poly(pp) + return poly.NewPolyFrom(Group, pp) } // Verify takes the vector of solution and makes the dot product with it such @@ -142,31 +145,31 @@ func (q *QAP) IsValid(sol Vector) bool { // now we try to verify if the equation is really satisfied by dividing eq / // z(x) : we should find no remainder _, rem := t.Div2(q.z) - if len(rem.Normalize()) > 0 { + if len(rem.Normalize().Coeffs()) > 0 { return false } return true } -func (q QAP) Quotient(sol Vector) Poly { +func (q QAP) Quotient(sol Vector) poly.Poly { // compute h(x) then evaluate it blindly at point s left, right, out := q.computeAggregatePoly(sol) // p(x) = t(x) * h(x) px := left.Mul(right).Sub(out) // h(x) = p(x) / t(x) hx, rem := px.Div2(q.z) - if len(rem.Normalize()) > 0 { + if len(rem.Normalize().Coeffs()) > 0 { panic("apocalypse") } return hx } -func (q QAP) computeAggregatePoly(sol Vector) (left Poly, right Poly, out Poly) { - left = Poly([]Element{}) - right = Poly([]Element{}) - out = Poly([]Element{}) +func (q QAP) computeAggregatePoly(sol Vector) (left poly.Poly, right poly.Poly, out poly.Poly) { + left = poly.NewZeroPoly(Group) + right = poly.NewZeroPoly(Group) + out = poly.NewZeroPoly(Group) for varIndex, val := range sol { - polyVal := Poly([]Element{val.ToFieldElement()}) + polyVal := poly.NewPolyFrom(Group, []Element{val.ToFieldElement()}) left = left.Add(q.left[varIndex].Mul(polyVal)) right = right.Add(q.right[varIndex].Mul(polyVal)) out = out.Add(q.out[varIndex].Mul(polyVal)) From 816a9ec888eed8fda1ebd1940205959d7adf5dc6 Mon Sep 17 00:00:00 2001 From: nikkolasg Date: Thu, 19 Nov 2020 18:00:59 +0000 Subject: [PATCH 3/4] pedersen open implementation --- pedersen/commit/commit.go | 16 ++-- pedersen/poly.go | 177 ++++++++++++++++++++++++++++++++------ polynomial/poly.go | 10 +++ 3 files changed, 168 insertions(+), 35 deletions(-) diff --git a/pedersen/commit/commit.go b/pedersen/commit/commit.go index 0d04c7f..dd75478 100644 --- a/pedersen/commit/commit.go +++ b/pedersen/commit/commit.go @@ -21,9 +21,9 @@ type Point = kyber.Point // Setup contains the setup information for pedersen commitments type Setup struct { - gs []Point - s Point - g Group + Gs []Point + S Point + G Group } // NewSetup returns the setup necessary to compute a commitment @@ -34,9 +34,9 @@ func NewSetup(g Group, l int) Setup { } s := g.Point().Pick(oracle(g, l, l+1)) return Setup{ - gs: gs, - s: s, - g: g, + Gs: gs, + S: s, + G: g, } } @@ -55,9 +55,9 @@ func oracle(g Group, l, pos int) cipher.Stream { // Commit compute the commitment of the points given the setup and the // randomness to use. func Commit(s Setup, msgs []Scalar, r Scalar) Point { - var acc = s.g.Point().Mul(r, s.s) + var acc = s.G.Point().Mul(r, s.S) for i, m := range msgs { - gxi := s.g.Point().Mul(m, s.gs[i]) + gxi := s.G.Point().Mul(m, s.Gs[i]) acc.Add(acc, gxi) } return acc diff --git a/pedersen/poly.go b/pedersen/poly.go index 8132a1b..65c6254 100644 --- a/pedersen/poly.go +++ b/pedersen/poly.go @@ -5,10 +5,12 @@ import ( "crypto/cipher" "crypto/sha256" "encoding/binary" + "math" "github.com/drand/kyber" "github.com/drand/kyber/util/random" "github.com/nikkolasg/playsnark/pedersen/commit" + poly "github.com/nikkolasg/playsnark/polynomial" ) // Group has points on it and can create scalar from the scalar fields @@ -22,6 +24,7 @@ type Point = kyber.Point type Setup struct { commit.Setup + g Group h Point degree int } @@ -35,6 +38,7 @@ func NewSetup(g Group, degree int) Setup { Setup: s, h: h, degree: degree, + g: g, } } @@ -43,44 +47,163 @@ type Polynomial = []Scalar type Commitment = Point // Commit the pedersen commitment of the coefficients -func Commit(s Setup, p Polynomial, r Scalar) Commitment { - return commit.Commit(s.Setup, p, r) +func Commit(s Setup, p poly.Poly, r Scalar) Commitment { + return commit.Commit(s.Setup, p.Coeffs(), r) } -func Open(s Setup, p Polynomial, c Commitment, z, r Scalar) Proof { - // v = p(z) - v := evalPoly(s.g, p, eval) +// Open strictly follows the algorithm description in Appendix A at +// https://eprint.iacr.org/2020/499.pdf +func Open(s Setup, p poly.Poly, c Commitment, z, w Scalar) Proof { + v := p.Eval(z) // random poly p2 such that p2(z) = 0 // naive strategy: create a random one, then eval p2(z) = a then substract // a from the free coefficient to p2' such that p2'(z) = p2(z) - a = 0 - p2 := randomPoly(s.g, s.degree) - a := evalPoly(s.g, p2, z) - p2[0] = p2[0].Sub(p2[0], a) + p2 := poly.RandomPoly(s.g, s.degree) + a := p2.Eval(z) + p2.Set(0, p2.Coeffs()[0].Sub(p2.Coeffs()[0], a)) // sample randomness w - w := s.g.Scalar().Pick(random.New()) - // commit to p2 using w - c2 := commit.Commit(s.Setup, p2, w) + w2 := s.g.Scalar().Pick(random.New()) + // commit to p2 using w - hiding commitment + c2 := commit.Commit(s.Setup, p2.Coeffs(), w) // compute challenge from random oracle - alpha := oracleFromScalars(c, z, v, c2) + alpha := s.g.Scalar().Pick(oracleFrom(s.g, c, z, v, c2)) + // then compute p3 = p + alpha * p2 + p2.Scale(alpha) + p3 := p.Add(p2) + // compute corresponding randomness w3 = w + alpha * w2 + w3 := s.g.Scalar() + w3.Mul(alpha, w2) + w3.Add(w3, w) + // compute non hiding commitment, i.e. we remove the first hiding of the + // commitment C + // c3 = c + alpha * c2 - w * S where w and S are respectively the randomness + // and the point used for making c in the first place -> we now have a + // commitment on p3 ! + ws := s.g.Point().Mul(w, s.Setup.S) + alphac2 := s.g.Point().Mul(alpha, c2) + c3 := s.g.Point().Add(c, alphac2) + c3.Add(c3, ws.Neg(ws)) + // Start computing challenges for the recursion and compute new random base + e0 := s.g.Scalar().Pick(oracleFrom(s.g, c3, z, v)) + h2 := s.g.Point().Mul(e0, s.h) + // initialize the vectors for the recursion + // coeffcients of p3 + cv := p3.Coeffs() + // powers of z: 1, z, z'2 ... + zv := make([]Scalar, len(cv)) + acc := s.g.Scalar().One() + for i := 1; i < len(zv); i++ { + zv[i] = acc.Mul(acc, z) + } + // base points of the pedersen commitment + gv := s.Setup.Gs + ei := e0 + // i: 1 -> log(degree+1) as we are doing a binary dissection of the three + // precedent vectors -- We assume length is power of two + max := int(math.Ceil(math.Log2(float64(p.Degree() + 1)))) + // slice of vector that will be filled at each iteration and returned in + // proof - they're the pedersen commitments of left and right part at each + // iteration + var lis []Point + var ris []Point + for i := 1; i <= max; i++ { + // append H2 to the left part of commitment basis for the pedersen + // commitments for the left part + l := append(cloneP(gv, 0, half(len(gv))), h2) + // inner product between the right part of c3 coefficients (polynomial + // created using the randomness) and left part of powers of z + leftCz := innerProd(cv[half(len(cv)):], zv[:half(len(zv))]) + leftSetup := commit.Setup{Gs: l, S: s.Setup.S, G: s.g} + li := commit.Commit(leftSetup, append(cloneS(cv, half(len(cv)), len(cv)), leftCz), w) + lis = append(lis, li) + // inner product between the left part of c3 coefficients and right part + // of power z + rightCz := innerProd(cv[:half(len(cv))], zv[half(len(zv)):]) + // commitment basis for the right pedersen commitments + r := append(cloneP(gv, half(len(gv)), len(gv)), h2) + rightSetup := commit.Setup{Gs: r, S: s.Setup.S, G: s.g} + ri := commit.Commit(rightSetup, append(cloneS(cv, 0, half(len(cv))), rightCz), w) + ris = append(ris, ri) + // get oracle from these commits + ei = s.g.Scalar().Pick(oracleFrom(s.g, ei, li, ri)) + // construct commitment key for next round: + // left part of gv + ei * right part of gv + pgv := cloneP(gv, half(len(gv)), len(gv)) + for i := 0; i < len(pgv); i++ { + pgv[i] = pgv[i].Mul(ei, pgv[i]) + } + gv = append(cloneP(gv, 0, half(len(gv))), pgv...) + // and inputs for next round cv and zv + // scale right part of cv by negative ei + pcv := cloneS(cv, half(len(cv)), len(cv)) + scale(pcv, s.g.Scalar().Neg(ei)) + // left(ci) + ei * right(ci) + cv = append(cloneS(cv, 0, half(len(cv))), pcv...) + // same for z + pzv := cloneS(zv, half(len(zv)), len(zv)) + scale(pzv, s.g.Scalar().Neg(ei)) + zv = append(cloneS(zv, 0, half(len(cv))), pzv...) + } + return Proof{ + U: gv, + C: cv, + C2: c2, + W: w, + Ri: ris, + Li: lis, + } +} +type Proof struct { + U []Point + C []Scalar + // Hiding commitment + C2 Point + // Commitment randomness + W Scalar + Ri []Point + Li []Point +} + +func cloneP(a []Point, low, high int) []Point { + b := make([]Point, high-low) + for i := low; i < high; i++ { + bi := i - low + b[bi] = a[i] + } + return b } -func evalPoly(g Group, p Polynomial, x Scalar) Scalar { - xi := x.Clone() - v := g.Scalar().Zero() - for j := len(p) - 1; j >= 0; j-- { - v.Mul(v, xi) - v.Add(v, p[j]) +func cloneS(s []Scalar, low, high int) []Scalar { + b := make([]Scalar, high-low) + for i := low; i < high; i++ { + bi := i - low + b[bi] = s[i] } - return v + return b } -func randomPoly(g Group, degree int) Polynomial { - p := make(Polynomial, degree+1) - for i := 0; i <= degree; i++ { - p[i] = g.Scalar().Pick(random.New()) +func scale(s []Scalar, f Scalar) { + for i := range s { + s[i] = s[i].Mul(s[i], f) + } +} + +// half assumes length is a power of two +func half(length int) int { + return length / 2 +} + +func innerProd(a, b []Scalar) Scalar { + if len(a) != len(b) { + panic("inner prod not possible") + } + sum := a[0].Clone().Zero() + tmp := a[0].Clone() + for i := 0; i < len(a); i++ { + sum = sum.Add(sum, tmp.Mul(a[i], b[i])) } - return p + return sum } // returns an oracle for deriving a point for pedersen polynomial commitment @@ -94,12 +217,12 @@ func oracle(g Group, degree int) cipher.Stream { return random.New(&b) } -func oracleFromScalar(g Group, scalars ...Scalar) cipher.Stream { +func oracleFrom(g Group, infos ...kyber.Marshaling) cipher.Stream { var h = sha256.New() h.Write([]byte("pedersen-polycommit-scalar")) h.Write([]byte(g.String())) - for _, s := range scalars { - s.WriteTo(h) + for _, s := range infos { + s.MarshalTo(h) } var b bytes.Buffer b.Write(h.Sum(nil)) diff --git a/polynomial/poly.go b/polynomial/poly.go index bb2a129..c90637e 100644 --- a/polynomial/poly.go +++ b/polynomial/poly.go @@ -40,6 +40,10 @@ func NewPolyFrom(g Group, coeffs []Scalar) Poly { return Poly{c: coeffs, g: g} } +func (p Poly) Set(pos int, coeff Scalar) { + p.c[pos] = coeff +} + func (p Poly) Mul(p2 Poly) Poly { l := len(p.c) + len(p2.c) - 1 output := make([]Scalar, l) @@ -55,6 +59,12 @@ func (p Poly) Mul(p2 Poly) Poly { return Poly{c: output, g: p.g} } +func (p Poly) Scale(a Scalar) { + for i := range p.c { + p.c[i] = p.c[i].Mul(p.c[i], a) + } +} + func (p Poly) Eval(i Scalar) Scalar { xi := i.Clone() v := p.g.Scalar().Zero() From b8d4efd9d967caec03ead7985fe7607605aa83f8 Mon Sep 17 00:00:00 2001 From: nikkolasg Date: Sat, 21 Nov 2020 21:38:39 +0000 Subject: [PATCH 4/4] almost working test... --- go.mod | 9 +- go.sum | 5 + pedersen/commit/commit.go | 20 ++- pedersen/commit/commit_test.go | 3 +- pedersen/poly.go | 269 +++++++++++++++++++++++++-------- pedersen/poly_test.go | 30 ++++ polynomial/poly.go | 5 +- 7 files changed, 263 insertions(+), 78 deletions(-) create mode 100644 pedersen/poly_test.go diff --git a/go.mod b/go.mod index 87806d4..9b3f70c 100644 --- a/go.mod +++ b/go.mod @@ -3,11 +3,12 @@ module github.com/nikkolasg/playsnark go 1.15 require ( - github.com/drand/kyber v1.1.3 - github.com/drand/kyber-bls12381 v0.2.1-0.20200920171356-02a6d1c7cc77 + github.com/drand/kyber v1.1.4 + github.com/drand/kyber-bls12381 v0.2.1 github.com/kilic/bls12-381 v0.0.0-20200820230200-6b2c19996391 // indirect github.com/stretchr/testify v1.4.0 go.dedis.ch/kyber/v3 v3.0.9 - golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a // indirect - golang.org/x/sys v0.0.0-20200918174421-af09f7315aff // indirect + golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a ) + +//replace github.com/drand/kyber => ../drand/kyber diff --git a/go.sum b/go.sum index 892b0bb..47ab95f 100644 --- a/go.sum +++ b/go.sum @@ -8,10 +8,13 @@ github.com/drand/kyber v1.0.2 h1:dHjtWJZJdn3zBBZ9pqLsLfcR9ScvDvSqzS1sWA8seao= github.com/drand/kyber v1.0.2/go.mod h1:x6KOpK7avKj0GJ4emhXFP5n7M7W7ChAPmnQh/OL6vRw= github.com/drand/kyber v1.1.3 h1:WrOOim4p2kap8tY4yJGO1S4lfk7shVPOkQkNZASVFnE= github.com/drand/kyber v1.1.3/go.mod h1:9+IgTq7kadePhZg7eRwSD7+bA+bmvqRK+8DtmoV5a3U= +github.com/drand/kyber v1.1.4 h1:YvKM03QWGvLrdTnYmxxP5iURAX+Gdb6qRDUOgg8i60Q= +github.com/drand/kyber v1.1.4/go.mod h1:9+IgTq7kadePhZg7eRwSD7+bA+bmvqRK+8DtmoV5a3U= github.com/drand/kyber-bls12381 v0.2.0 h1:3GJfiHaMggQS2l2n7yrfX0PjY9BYikLM2f0zKP1eZTs= github.com/drand/kyber-bls12381 v0.2.0/go.mod h1:zQip/bHdeEB6HFZSU3v+d3cQE0GaBVQw9aR2E7AdoeI= github.com/drand/kyber-bls12381 v0.2.1-0.20200920171356-02a6d1c7cc77 h1:imrDAsUj7tyTTht3pMX67Akc6CYy3vGiS10PIC6nh6g= github.com/drand/kyber-bls12381 v0.2.1-0.20200920171356-02a6d1c7cc77/go.mod h1:zQip/bHdeEB6HFZSU3v+d3cQE0GaBVQw9aR2E7AdoeI= +github.com/drand/kyber-bls12381 v0.2.1/go.mod h1:JwWn4nHO9Mp4F5qCie5sVIPQZ0X6cw8XAeMRvc/GXBE= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/kilic/bls12-381 v0.0.0-20200607163746-32e1441c8a9f h1:qET3Wx0v8tMtoTOQnsJXVvqvCopSf48qobR6tcJuDHo= github.com/kilic/bls12-381 v0.0.0-20200607163746-32e1441c8a9f/go.mod h1:XXfR6YFCRSrkEXbNlIyDsgXVNJWVUV30m/ebkVy9n6s= @@ -55,6 +58,8 @@ golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200812155832-6a926be9bd1d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200918174421-af09f7315aff h1:1CPUrky56AcgSpxz/KfgzQWzfG09u5YOL8MvPYBlrL8= golang.org/x/sys v0.0.0-20200918174421-af09f7315aff/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200926100807-9d91bd62050c h1:38q6VNPWR010vN82/SB121GujZNIfAUb4YttE2rhGuc= +golang.org/x/sys v0.0.0-20200926100807-9d91bd62050c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/pedersen/commit/commit.go b/pedersen/commit/commit.go index dd75478..32b6033 100644 --- a/pedersen/commit/commit.go +++ b/pedersen/commit/commit.go @@ -1,13 +1,13 @@ package commit import ( - "bytes" "crypto/cipher" "crypto/sha256" "encoding/binary" "github.com/drand/kyber" "github.com/drand/kyber/util/random" + "golang.org/x/crypto/blake2s" ) // Group has points on it and can create scalar from the scalar fields @@ -45,16 +45,22 @@ func oracle(g Group, l, pos int) cipher.Stream { var h = sha256.New() h.Write([]byte("pedersen-commit")) h.Write([]byte(g.String())) - binary.Write(h, binary.LittleEndian, l) - binary.Write(h, binary.LittleEndian, pos) - var b bytes.Buffer - b.Write(h.Sum(nil)) - return random.New(&b) + binary.Write(h, binary.LittleEndian, int32(l)) + binary.Write(h, binary.LittleEndian, int32(pos)) + xof, err := blake2s.NewXOF(0, h.Sum(nil)) + if err != nil { + panic(err) + } + return random.New(xof) } // Commit compute the commitment of the points given the setup and the -// randomness to use. +// randomness to use. If r == nil, then r is set 0, so the commitment is not +// hiding anymore. func Commit(s Setup, msgs []Scalar, r Scalar) Point { + if r == nil { + r = s.G.Scalar().Zero() + } var acc = s.G.Point().Mul(r, s.S) for i, m := range msgs { gxi := s.G.Point().Mul(m, s.Gs[i]) diff --git a/pedersen/commit/commit_test.go b/pedersen/commit/commit_test.go index 036e79c..427a9d2 100644 --- a/pedersen/commit/commit_test.go +++ b/pedersen/commit/commit_test.go @@ -8,8 +8,9 @@ import ( "github.com/stretchr/testify/require" ) +var g = edwards25519.NewBlakeSHA256Ed25519() + func TestCommit(t *testing.T) { - var g = edwards25519.NewBlakeSHA256Ed25519() var l = 10 setup := NewSetup(g, l) msgs := make([]Scalar, l-1) diff --git a/pedersen/poly.go b/pedersen/poly.go index 65c6254..97a4f80 100644 --- a/pedersen/poly.go +++ b/pedersen/poly.go @@ -1,16 +1,17 @@ package pedersen import ( - "bytes" "crypto/cipher" "crypto/sha256" "encoding/binary" + "fmt" "math" "github.com/drand/kyber" "github.com/drand/kyber/util/random" "github.com/nikkolasg/playsnark/pedersen/commit" poly "github.com/nikkolasg/playsnark/polynomial" + "golang.org/x/crypto/blake2s" ) // Group has points on it and can create scalar from the scalar fields @@ -33,6 +34,7 @@ type Setup struct { // commitment func NewSetup(g Group, degree int) Setup { s := commit.NewSetup(g, degree+1) + fmt.Println("SO FAR SO GOOD") h := g.Point().Pick(oracle(g, degree)) return Setup{ Setup: s, @@ -47,56 +49,65 @@ type Polynomial = []Scalar type Commitment = Point // Commit the pedersen commitment of the coefficients -func Commit(s Setup, p poly.Poly, r Scalar) Commitment { - return commit.Commit(s.Setup, p.Coeffs(), r) +func Commit(s Setup, p poly.Poly, w Scalar) Commitment { + return commit.Commit(s.Setup, p.Coeffs(), w) } // Open strictly follows the algorithm description in Appendix A at // https://eprint.iacr.org/2020/499.pdf func Open(s Setup, p poly.Poly, c Commitment, z, w Scalar) Proof { + // 1. v = p(z) v := p.Eval(z) - // random poly p2 such that p2(z) = 0 + // 2. sample random poly p2 such that p2(z) = 0 // naive strategy: create a random one, then eval p2(z) = a then substract // a from the free coefficient to p2' such that p2'(z) = p2(z) - a = 0 p2 := poly.RandomPoly(s.g, s.degree) a := p2.Eval(z) p2.Set(0, p2.Coeffs()[0].Sub(p2.Coeffs()[0], a)) - // sample randomness w + assert(p2.Eval(z).Equal(s.g.Scalar().Zero()), "p2(z) != 0") + // 3. sample commitment randomness w2 w2 := s.g.Scalar().Pick(random.New()) - // commit to p2 using w - hiding commitment - c2 := commit.Commit(s.Setup, p2.Coeffs(), w) - // compute challenge from random oracle + // 4. commit to p2 using w2 - hiding commitment + c2 := commit.Commit(s.Setup, p2.Coeffs(), w2) + // 5. compute challenge from random oracle alpha := s.g.Scalar().Pick(oracleFrom(s.g, c, z, v, c2)) - // then compute p3 = p + alpha * p2 + // 6. then compute p3 = p + alpha * p2 p2.Scale(alpha) p3 := p.Add(p2) - // compute corresponding randomness w3 = w + alpha * w2 + // p3(z) = p(z) + alpha * p2(z) = v + 0 + assert(p3.Eval(z).Equal(v), "p3 not correct") + // 7. compute corresponding randomness w3 = w + alpha * w2 w3 := s.g.Scalar() - w3.Mul(alpha, w2) - w3.Add(w3, w) - // compute non hiding commitment, i.e. we remove the first hiding of the - // commitment C - // c3 = c + alpha * c2 - w * S where w and S are respectively the randomness - // and the point used for making c in the first place -> we now have a - // commitment on p3 ! - ws := s.g.Point().Mul(w, s.Setup.S) + w3 = w3.Mul(alpha, w2) + w3 = w3.Add(w3, w) + // 8. compute non hiding commitment to p3 without computing it from scratch + // Commit(p3) = SUM p3_i * Gi = SUM (p_i + alpha * p2_i) * Gi + // C + alpha * C2 - w3 * S = + // w*S + SUM p_i*Gi + alpha * (w2*S + SUM p2_i*Gi) - wS - alpha*w2*S = + // SUM (p_i + alpha * p2_i) * Gi + ws := s.g.Point().Mul(w3, s.Setup.S) alphac2 := s.g.Point().Mul(alpha, c2) c3 := s.g.Point().Add(c, alphac2) - c3.Add(c3, ws.Neg(ws)) + c3 = c3.Add(c3, ws.Neg(ws)) + assert(c3.Equal(commit.Commit(s.Setup, p3.Coeffs(), nil)), "c3 not correct") + // -------------- // Start computing challenges for the recursion and compute new random base e0 := s.g.Scalar().Pick(oracleFrom(s.g, c3, z, v)) h2 := s.g.Point().Mul(e0, s.h) + fmt.Println("PROOF H2 : ", h2) // initialize the vectors for the recursion // coeffcients of p3 cv := p3.Coeffs() // powers of z: 1, z, z'2 ... zv := make([]Scalar, len(cv)) acc := s.g.Scalar().One() + zv[0] = acc.Clone() for i := 1; i < len(zv); i++ { zv[i] = acc.Mul(acc, z) } // base points of the pedersen commitment gv := s.Setup.Gs + assert(len(gv) == len(cv), "gs incompatible length") ei := e0 // i: 1 -> log(degree+1) as we are doing a binary dissection of the three // precedent vectors -- We assume length is power of two @@ -107,56 +118,61 @@ func Open(s Setup, p poly.Poly, c Commitment, z, w Scalar) Proof { var lis []Point var ris []Point for i := 1; i <= max; i++ { + // 1. // append H2 to the left part of commitment basis for the pedersen // commitments for the left part - l := append(cloneP(gv, 0, half(len(gv))), h2) + l := append(cloneP(leftP(gv)), h2) // inner product between the right part of c3 coefficients (polynomial // created using the randomness) and left part of powers of z - leftCz := innerProd(cv[half(len(cv)):], zv[:half(len(zv))]) + leftCz := innerProd(right(cv), left(zv)) leftSetup := commit.Setup{Gs: l, S: s.Setup.S, G: s.g} - li := commit.Commit(leftSetup, append(cloneS(cv, half(len(cv)), len(cv)), leftCz), w) + li := commit.Commit(leftSetup, append(cloneS(right(cv)), leftCz), nil) lis = append(lis, li) + // 2. // inner product between the left part of c3 coefficients and right part // of power z - rightCz := innerProd(cv[:half(len(cv))], zv[half(len(zv)):]) + rightCz := innerProd(left(cv), right(zv)) // commitment basis for the right pedersen commitments - r := append(cloneP(gv, half(len(gv)), len(gv)), h2) + r := append(cloneP(rightP(gv)), h2) rightSetup := commit.Setup{Gs: r, S: s.Setup.S, G: s.g} - ri := commit.Commit(rightSetup, append(cloneS(cv, 0, half(len(cv))), rightCz), w) + ri := commit.Commit(rightSetup, append(cloneS(left(cv)), rightCz), nil) ris = append(ris, ri) - // get oracle from these commits + // 3. get oracle from these commits ei = s.g.Scalar().Pick(oracleFrom(s.g, ei, li, ri)) - // construct commitment key for next round: + fmt.Println("PROOF: i", i, " ei = ", ei) + // 4. construct commitment key for next round: // left part of gv + ei * right part of gv - pgv := cloneP(gv, half(len(gv)), len(gv)) + pgv := cloneP(rightP(gv)) for i := 0; i < len(pgv); i++ { pgv[i] = pgv[i].Mul(ei, pgv[i]) } - gv = append(cloneP(gv, 0, half(len(gv))), pgv...) - // and inputs for next round cv and zv + gv = sumVecP(cloneP(leftP(gv)), pgv) + // 5. and inputs for next round cv and zv // scale right part of cv by negative ei - pcv := cloneS(cv, half(len(cv)), len(cv)) + pcv := cloneS(right(cv)) scale(pcv, s.g.Scalar().Neg(ei)) // left(ci) + ei * right(ci) - cv = append(cloneS(cv, 0, half(len(cv))), pcv...) - // same for z - pzv := cloneS(zv, half(len(zv)), len(zv)) - scale(pzv, s.g.Scalar().Neg(ei)) - zv = append(cloneS(zv, 0, half(len(cv))), pzv...) + cv = sumVecS(cloneS(left(cv)), pcv) + // 6. same for z + pzv := cloneS(right(zv)) + scale(pzv, ei) + zv = sumVecS(cloneS(left(zv)), pzv) } + assert(len(cv) == 1, "binary dissection is off - len %d", len(cv)) + assert(len(gv) == 1, "binary dissection is off - len %d", len(gv)) return Proof{ - U: gv, - C: cv, + U: gv[0], + C: cv[0], C2: c2, - W: w, + W: w3, Ri: ris, Li: lis, } } type Proof struct { - U []Point - C []Scalar + U Point + C Scalar // Hiding commitment C2 Point // Commitment randomness @@ -165,21 +181,122 @@ type Proof struct { Li []Point } -func cloneP(a []Point, low, high int) []Point { - b := make([]Point, high-low) - for i := low; i < high; i++ { - bi := i - low - b[bi] = a[i] +// Verify takes the setup, c which is the commitment of the polynomial, the +// evaluation point z, the alleged evaluation v, and a proof p of opening. +// It returns true if the proof is correct, false otherwise. The check performs +// the verification routine outlined in the Check section, appendix A.2 of the +// paper. +func Verify(s Setup, C Commitment, z, v Scalar, p Proof) bool { + // 1. 2. + d2 := len(s.Setup.Gs) - 1 + // 3. 4. - this is the log(n) check + h, ok := SuccinctCheck(s.g, s.Setup.S, s.h, d2, C, s.degree, z, v, p) + if !ok { + return false } - return b + // 5. this is the linear check as commiting is O(n) + exp := commit.Commit(s.Setup, h.Coeffs(), nil) + if !exp.Equal(p.U) { + return false + } + return true } -func cloneS(s []Scalar, low, high int) []Scalar { - b := make([]Scalar, high-low) - for i := low; i < high; i++ { - bi := i - low - b[bi] = s[i] +func SuccinctCheck(g Group, S, H Point, d2 int, C Point, d int, z, v Scalar, p Proof) (*poly.Poly, bool) { + // 2. + if d != d2 { + fmt.Println("YOLO") + return nil, false + } + // 3. compute challenge alpha as in the proving part + alpha := g.Scalar().Pick(oracleFrom(g, C, z, v, p.C2)) + // 4. compute non hiding commitment as in the proving part + ws := g.Point().Mul(p.W, S) + alphac2 := g.Point().Mul(alpha, p.C2) + c3 := g.Point().Add(C, alphac2) + c3 = c3.Add(c3, ws.Neg(ws)) + // 5. Compute first challenge and set new base for H + max := int(math.Ceil(math.Log2(float64(d + 1)))) + // keep track of all eis generated for later use to construct the verifying + // polynomial + var eis = make([]Scalar, 0, max) + ei := g.Scalar().Pick(oracleFrom(g, c3, z, v)) + H2 := g.Point().Mul(ei, H) + fmt.Println("CHECK H2: ", H2) + // 6. Compute first group element C0 = C3 + v * H2 + ci := g.Point().Add(c3, g.Point().Mul(v, H2)) + // 7. Recursion step + for i := 1; i <= max; i++ { + // (a) Generate new challenge from previous challenge and L_i and R_i values + // as in the proof + li := p.Li[i-1] // we are offsetting by one since there is no R0 or L0 + ri := p.Ri[i-1] + ei = g.Scalar().Pick(oracleFrom(g, ei, li, ri)) + fmt.Println("CHECK: i", i, " ei = ", ei) + eis = append(eis, ei) + // (b) compute new commitment + // ei^-1 * Li + C_i-1 + ei * Ri + nei := g.Scalar().Neg(ei) + neili := g.Point().Mul(nei, li) + eiri := g.Point().Mul(ei, ri) + ci = g.Point().Add(ci, g.Point().Add(neili, eiri)) } + // 8. define polynomial h(x) with eis and evaluate it to z. We construct + // each term as polynomial and multiply them + // set up an accumulator polynomial init to p(x) = 1 to multiply along the + // iteration + // SUM (i:0 -> log(d+1)-1) (1 + ei * X^2^i) = + // (1 + ei * X) * (...) * (1 + ei * X^2^log(d+1)-1) + acc := poly.NewZeroPoly(g, 0) + acc.Set(0, g.Scalar().SetInt64(1)) + for i := 0; i < max; i++ { + power := 1 << i + // 1 + e_(log(d+1) - i) * X^2^i + p := poly.NewZeroPoly(g, power) + fmt.Printf("constructing power %d -> len(coeffs) = %d\n", power, len(p.Coeffs())) + p.Set(power, eis[max-1-i]) // -1 because of array offset + p.Set(0, g.Scalar().SetInt64(1)) + acc = acc.Mul(p) + } + fmt.Println("final poly has degree ", acc.Degree()) + // 9. eval v' = acc(z) * c + v2 := g.Scalar().Mul(p.C, acc.Eval(z)) + // 10. Compute pedersen commitment over c || v' using special basis + // i.e. c * U + c * h(z) * H2 + setup := commit.Setup{Gs: []Point{p.U, H2}, G: g, S: S} + computed := commit.Commit(setup, []Scalar{p.C, v2}, nil) + expected := ci + if !computed.Equal(expected) { + fmt.Println("HERE IT?S GONE WRONG") + return nil, false + } + return &acc, true +} + +func left(a []Scalar) []Scalar { + return a[:half(len(a))] +} + +func right(a []Scalar) []Scalar { + return a[half(len(a)):] +} + +func leftP(p []Point) []Point { + return p[:half(len(p))] +} +func rightP(p []Point) []Point { + return p[half(len(p)):] +} + +func cloneP(a []Point) []Point { + b := make([]Point, len(a)) + copy(b, a) + return b +} + +func cloneS(s []Scalar) []Scalar { + b := make([]Scalar, len(s)) + copy(b, s) return b } @@ -195,9 +312,7 @@ func half(length int) int { } func innerProd(a, b []Scalar) Scalar { - if len(a) != len(b) { - panic("inner prod not possible") - } + assert(len(a) == len(b), "inner prod not possible") sum := a[0].Clone().Zero() tmp := a[0].Clone() for i := 0; i < len(a); i++ { @@ -206,15 +321,39 @@ func innerProd(a, b []Scalar) Scalar { return sum } -// returns an oracle for deriving a point for pedersen polynomial commitment -func oracle(g Group, degree int) cipher.Stream { +// IN PLACE +// Damn generics in Go ... +func sumVecS(a, b []Scalar) []Scalar { + assert(len(a) == len(b), "sumVecS not possible") + for i := range a { + a[i] = a[i].Add(a[i], b[i]) + } + return a +} + +func sumVecP(a, b []Point) []Point { + assert(len(a) == len(b), "sumVecP not possible") + for i := range a { + a[i] = a[i].Add(a[i], b[i]) + } + return a +} + +func assert(cond bool, msg string, vars ...interface{}) { + if !cond { + panic(fmt.Sprintf(msg, vars...)) + } +} + +// returns an oracle for deriving a point for pedersen commitment +func oracle(g Group, l int) cipher.Stream { var h = sha256.New() - h.Write([]byte("pedersen-polycommit")) + h.Write([]byte("pedersen-pcommit")) h.Write([]byte(g.String())) - binary.Write(h, binary.LittleEndian, degree) - var b bytes.Buffer - b.Write(h.Sum(nil)) - return random.New(&b) + binary.Write(h, binary.LittleEndian, int32(l)) + xof, err := blake2s.NewXOF(0, h.Sum(nil)) + assert(err == nil, "failed oracle") + return random.New(xof) } func oracleFrom(g Group, infos ...kyber.Marshaling) cipher.Stream { @@ -224,7 +363,7 @@ func oracleFrom(g Group, infos ...kyber.Marshaling) cipher.Stream { for _, s := range infos { s.MarshalTo(h) } - var b bytes.Buffer - b.Write(h.Sum(nil)) - return random.New(&b) + xof, err := blake2s.NewXOF(0, h.Sum(nil)) + assert(err == nil, "failed oracle") + return random.New(xof) } diff --git a/pedersen/poly_test.go b/pedersen/poly_test.go new file mode 100644 index 0000000..2d020d7 --- /dev/null +++ b/pedersen/poly_test.go @@ -0,0 +1,30 @@ +package pedersen + +import ( + "testing" + + "github.com/drand/kyber/group/edwards25519" + "github.com/drand/kyber/util/random" + poly "github.com/nikkolasg/playsnark/polynomial" + "github.com/stretchr/testify/require" +) + +var g = edwards25519.NewBlakeSHA256Ed25519() + +func TestPedersenPolyCommit(t *testing.T) { + degree := 3 // d+1 must be power of two + setup := NewSetup(g, degree) + // polynomial to commit to + p := poly.RandomPoly(g, degree) + // randomness used to commit + w := g.Scalar().Pick(random.New()) + // commitment to the polynomial + commitment := Commit(setup, p, w) + // evaluation point we wish to evaluate and open our polynomial + z := g.Scalar().Pick(random.New()) + v := p.Eval(z) + // opening of the commitment for the given evaluation point + proof := Open(setup, p, commitment, z, w) + // verification of the correct opening + require.True(t, Verify(setup, commitment, z, v, proof)) +} diff --git a/polynomial/poly.go b/polynomial/poly.go index c90637e..d9d0b98 100644 --- a/polynomial/poly.go +++ b/polynomial/poly.go @@ -32,7 +32,10 @@ func emptyPoly(g Group, d int) Poly { return Poly{c: o, g: g} } -func NewZeroPoly(g Group) Poly { +func NewZeroPoly(g Group, degree ...int) Poly { + if len(degree) > 0 { + return emptyPoly(g, degree[0]) + } return Poly{c: []Scalar{}, g: g} }