diff --git a/go.mod b/go.mod index 062037f1de..c75380e104 100644 --- a/go.mod +++ b/go.mod @@ -7,9 +7,9 @@ toolchain go1.22.6 require ( github.com/bits-and-blooms/bitset v1.14.2 github.com/blang/semver/v4 v4.0.0 - github.com/consensys/bavard v0.1.15 + github.com/consensys/bavard v0.1.22 github.com/consensys/compress v0.2.5 - github.com/consensys/gnark-crypto v0.14.1-0.20241002214024-485db50997ef + github.com/consensys/gnark-crypto v0.14.1-0.20241010154951-6638408a49f3 github.com/fxamacker/cbor/v2 v2.7.0 github.com/google/go-cmp v0.6.0 github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 diff --git a/go.sum b/go.sum index a705f27256..1960733dee 100644 --- a/go.sum +++ b/go.sum @@ -57,12 +57,12 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/consensys/bavard v0.1.15 h1:fxv2mg1afRMJvZgpwEgLmyr2MsQwaAYcyKf31UBHzw4= -github.com/consensys/bavard v0.1.15/go.mod h1:9ItSMtA/dXMAiL7BG6bqW2m3NdSEObYWoH223nGHukI= +github.com/consensys/bavard v0.1.22 h1:Uw2CGvbXSZWhqK59X0VG/zOjpTFuOMcPLStrp1ihI0A= +github.com/consensys/bavard v0.1.22/go.mod h1:k/zVjHHC4B+PQy1Pg7fgvG3ALicQw540Crag8qx+dZs= github.com/consensys/compress v0.2.5 h1:gJr1hKzbOD36JFsF1AN8lfXz1yevnJi1YolffY19Ntk= github.com/consensys/compress v0.2.5/go.mod h1:pyM+ZXiNUh7/0+AUjUf9RKUM6vSH7T/fsn5LLS0j1Tk= -github.com/consensys/gnark-crypto v0.14.1-0.20241002214024-485db50997ef h1:ZK7HNEFMkTslyLKLbWpDATuZYUWbOcjm8yl50rL9XdQ= -github.com/consensys/gnark-crypto v0.14.1-0.20241002214024-485db50997ef/go.mod h1:AL8vs/7MyZ0P93tcNDkUWVwf2rWLUGFUP/1iqiF7h4E= +github.com/consensys/gnark-crypto v0.14.1-0.20241010154951-6638408a49f3 h1:jVatckGR1s3OHs4QnGsppX+w2P3eedlWxi7ZFq56rjA= +github.com/consensys/gnark-crypto v0.14.1-0.20241010154951-6638408a49f3/go.mod h1:F/hJyWBcTr1sWeifAKfEN3aVb3G4U5zheEC8IbWQun4= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= diff --git a/internal/generator/backend/main.go b/internal/generator/backend/main.go index 999ce79dde..38ed29bc5e 100644 --- a/internal/generator/backend/main.go +++ b/internal/generator/backend/main.go @@ -75,7 +75,7 @@ func main() { if err != nil { panic(err) } - if err := generator.GenerateFF(tinyfieldConf, tiny_field.RootPath); err != nil { + if err := generator.GenerateFF(tinyfieldConf, tiny_field.RootPath, "", ""); err != nil { panic(err) } diff --git a/internal/tinyfield/element.go b/internal/tinyfield/element.go index 85d289e751..5b3bce6659 100644 --- a/internal/tinyfield/element.go +++ b/internal/tinyfield/element.go @@ -404,32 +404,8 @@ func (z *Element) Select(c int, x0 *Element, x1 *Element) *Element { // and is used for testing purposes. func _mulGeneric(z, x, y *Element) { - // Implements CIOS multiplication -- section 2.3.2 of Tolga Acar's thesis - // https://www.microsoft.com/en-us/research/wp-content/uploads/1998/06/97Acar.pdf - // - // The algorithm: - // - // for i=0 to N-1 - // C := 0 - // for j=0 to N-1 - // (C,t[j]) := t[j] + x[j]*y[i] + C - // (t[N+1],t[N]) := t[N] + C - // - // C := 0 - // m := t[0]*q'[0] mod D - // (C,_) := t[0] + m*q[0] - // for j=1 to N-1 - // (C,t[j-1]) := t[j] + m*q[j] + C - // - // (C,t[N-1]) := t[N] + C - // t[N] := t[N+1] + C - // - // → N is the number of machine words needed to store the modulus q - // → D is the word size. For example, on a 64-bit architecture D is 2 64 - // → x[i], y[i], q[i] is the ith word of the numbers x,y,q - // → q'[0] is the lowest word of the number -q⁻¹ mod r. This quantity is pre-computed, as it does not depend on the inputs. - // → t is a temporary array of size N+2 - // → C, S are machine words. A pair (C,S) refers to (hi-bits, lo-bits) of a two-word number + // Algorithm 2 of "Faster Montgomery Multiplication and Multi-Scalar-Multiplication for SNARKS" + // by Y. El Housni and G. Botrel https://doi.org/10.46586/tches.v2023.i3.504-521 var t [2]uint64 var D uint64 diff --git a/internal/tinyfield/element_test.go b/internal/tinyfield/element_test.go index ca73355dc9..85dcdcd893 100644 --- a/internal/tinyfield/element_test.go +++ b/internal/tinyfield/element_test.go @@ -582,7 +582,6 @@ func TestElementBitLen(t *testing.T) { )) properties.TestingRun(t, gopter.ConsoleReporter(false)) - } func TestElementButterflies(t *testing.T) { @@ -652,77 +651,6 @@ func TestElementLexicographicallyLargest(t *testing.T) { } -func TestElementVecOps(t *testing.T) { - assert := require.New(t) - - const N = 7 - a := make(Vector, N) - b := make(Vector, N) - c := make(Vector, N) - for i := 0; i < N; i++ { - a[i].SetRandom() - b[i].SetRandom() - } - - // Vector addition - c.Add(a, b) - for i := 0; i < N; i++ { - var expected Element - expected.Add(&a[i], &b[i]) - assert.True(c[i].Equal(&expected), "Vector addition failed") - } - - // Vector subtraction - c.Sub(a, b) - for i := 0; i < N; i++ { - var expected Element - expected.Sub(&a[i], &b[i]) - assert.True(c[i].Equal(&expected), "Vector subtraction failed") - } - - // Vector scaling - c.ScalarMul(a, &b[0]) - for i := 0; i < N; i++ { - var expected Element - expected.Mul(&a[i], &b[0]) - assert.True(c[i].Equal(&expected), "Vector scaling failed") - } -} - -func BenchmarkElementVecOps(b *testing.B) { - // note; to benchmark against "no asm" version, use the following - // build tag: -tags purego - const N = 1024 - a1 := make(Vector, N) - b1 := make(Vector, N) - c1 := make(Vector, N) - for i := 0; i < N; i++ { - a1[i].SetRandom() - b1[i].SetRandom() - } - - b.Run("Add", func(b *testing.B) { - b.ResetTimer() - for i := 0; i < b.N; i++ { - c1.Add(a1, b1) - } - }) - - b.Run("Sub", func(b *testing.B) { - b.ResetTimer() - for i := 0; i < b.N; i++ { - c1.Sub(a1, b1) - } - }) - - b.Run("ScalarMul", func(b *testing.B) { - b.ResetTimer() - for i := 0; i < b.N; i++ { - c1.ScalarMul(a1, &b1[0]) - } - }) -} - func TestElementAdd(t *testing.T) { t.Parallel() parameters := gopter.DefaultTestParameters() @@ -2230,32 +2158,32 @@ func gen() gopter.Gen { } } -func genFull() gopter.Gen { - return func(genParams *gopter.GenParameters) *gopter.GenResult { +func genRandomFq(genParams *gopter.GenParameters) Element { + var g Element - genRandomFq := func() Element { - var g Element + g = Element{ + genParams.NextUint64(), + } - g = Element{ - genParams.NextUint64(), - } + if qElement[0] != ^uint64(0) { + g[0] %= (qElement[0] + 1) + } - if qElement[0] != ^uint64(0) { - g[0] %= (qElement[0] + 1) - } + for !g.smallerThanModulus() { + g = Element{ + genParams.NextUint64(), + } + if qElement[0] != ^uint64(0) { + g[0] %= (qElement[0] + 1) + } + } - for !g.smallerThanModulus() { - g = Element{ - genParams.NextUint64(), - } - if qElement[0] != ^uint64(0) { - g[0] %= (qElement[0] + 1) - } - } + return g +} - return g - } - a := genRandomFq() +func genFull() gopter.Gen { + return func(genParams *gopter.GenParameters) *gopter.GenResult { + a := genRandomFq(genParams) var carry uint64 a[0], _ = bits.Add64(a[0], qElement[0], carry) @@ -2264,3 +2192,11 @@ func genFull() gopter.Gen { return genResult } } + +func genElement() gopter.Gen { + return func(genParams *gopter.GenParameters) *gopter.GenResult { + a := genRandomFq(genParams) + genResult := gopter.NewGenResult(a, gopter.NoShrinker) + return genResult + } +} diff --git a/internal/tinyfield/vector.go b/internal/tinyfield/vector.go index 69da421988..703832e720 100644 --- a/internal/tinyfield/vector.go +++ b/internal/tinyfield/vector.go @@ -214,6 +214,25 @@ func (vector *Vector) ScalarMul(a Vector, b *Element) { scalarMulVecGeneric(*vector, a, b) } +// Sum computes the sum of all elements in the vector. +func (vector *Vector) Sum() (res Element) { + sumVecGeneric(&res, *vector) + return +} + +// InnerProduct computes the inner product of two vectors. +// It panics if the vectors don't have the same length. +func (vector *Vector) InnerProduct(other Vector) (res Element) { + innerProductVecGeneric(&res, *vector, other) + return +} + +// Mul multiplies two vectors element-wise and stores the result in self. +// It panics if the vectors don't have the same length. +func (vector *Vector) Mul(a, b Vector) { + mulVecGeneric(*vector, a, b) +} + func addVecGeneric(res, a, b Vector) { if len(a) != len(b) || len(a) != len(res) { panic("vector.Add: vectors don't have the same length") @@ -241,6 +260,32 @@ func scalarMulVecGeneric(res, a Vector, b *Element) { } } +func sumVecGeneric(res *Element, a Vector) { + for i := 0; i < len(a); i++ { + res.Add(res, &a[i]) + } +} + +func innerProductVecGeneric(res *Element, a, b Vector) { + if len(a) != len(b) { + panic("vector.InnerProduct: vectors don't have the same length") + } + var tmp Element + for i := 0; i < len(a); i++ { + tmp.Mul(&a[i], &b[i]) + res.Add(res, &tmp) + } +} + +func mulVecGeneric(res, a, b Vector) { + if len(a) != len(b) || len(a) != len(res) { + panic("vector.Mul: vectors don't have the same length") + } + for i := 0; i < len(a); i++ { + res[i].Mul(&a[i], &b[i]) + } +} + // TODO @gbotrel make a public package out of that. // execute executes the work function in parallel. // this is copy paste from internal/parallel/parallel.go diff --git a/internal/tinyfield/vector_test.go b/internal/tinyfield/vector_test.go index 68a98e5fa9..fc02cc1279 100644 --- a/internal/tinyfield/vector_test.go +++ b/internal/tinyfield/vector_test.go @@ -18,10 +18,15 @@ package tinyfield import ( "bytes" + "fmt" "github.com/stretchr/testify/require" + "os" "reflect" "sort" "testing" + + "github.com/leanovate/gopter" + "github.com/leanovate/gopter/prop" ) func TestVectorSort(t *testing.T) { @@ -88,3 +93,273 @@ func (vector *Vector) unmarshalBinaryAsync(data []byte) error { } return <-chErr } + +func TestVectorOps(t *testing.T) { + parameters := gopter.DefaultTestParameters() + if testing.Short() { + parameters.MinSuccessfulTests = 2 + } else { + parameters.MinSuccessfulTests = 10 + } + properties := gopter.NewProperties(parameters) + + addVector := func(a, b Vector) bool { + c := make(Vector, len(a)) + c.Add(a, b) + + for i := 0; i < len(a); i++ { + var tmp Element + tmp.Add(&a[i], &b[i]) + if !tmp.Equal(&c[i]) { + return false + } + } + return true + } + + subVector := func(a, b Vector) bool { + c := make(Vector, len(a)) + c.Sub(a, b) + + for i := 0; i < len(a); i++ { + var tmp Element + tmp.Sub(&a[i], &b[i]) + if !tmp.Equal(&c[i]) { + return false + } + } + return true + } + + scalarMulVector := func(a Vector, b Element) bool { + c := make(Vector, len(a)) + c.ScalarMul(a, &b) + + for i := 0; i < len(a); i++ { + var tmp Element + tmp.Mul(&a[i], &b) + if !tmp.Equal(&c[i]) { + return false + } + } + return true + } + + sumVector := func(a Vector) bool { + var sum Element + computed := a.Sum() + for i := 0; i < len(a); i++ { + sum.Add(&sum, &a[i]) + } + + return sum.Equal(&computed) + } + + innerProductVector := func(a, b Vector) bool { + computed := a.InnerProduct(b) + var innerProduct Element + for i := 0; i < len(a); i++ { + var tmp Element + tmp.Mul(&a[i], &b[i]) + innerProduct.Add(&innerProduct, &tmp) + } + + return innerProduct.Equal(&computed) + } + + mulVector := func(a, b Vector) bool { + c := make(Vector, len(a)) + a[0].SetUint64(0x24) + b[0].SetUint64(0x42) + c.Mul(a, b) + + for i := 0; i < len(a); i++ { + var tmp Element + tmp.Mul(&a[i], &b[i]) + if !tmp.Equal(&c[i]) { + return false + } + } + return true + } + + sizes := []int{1, 2, 3, 4, 8, 9, 15, 16, 509, 510, 511, 512, 513, 514} + type genPair struct { + g1, g2 gopter.Gen + label string + } + + for _, size := range sizes { + generators := []genPair{ + {genZeroVector(size), genZeroVector(size), "zero vectors"}, + {genMaxVector(size), genMaxVector(size), "max vectors"}, + {genVector(size), genVector(size), "random vectors"}, + {genVector(size), genZeroVector(size), "random and zero vectors"}, + } + for _, gp := range generators { + properties.Property(fmt.Sprintf("vector addition %d - %s", size, gp.label), prop.ForAll( + addVector, + gp.g1, + gp.g2, + )) + + properties.Property(fmt.Sprintf("vector subtraction %d - %s", size, gp.label), prop.ForAll( + subVector, + gp.g1, + gp.g2, + )) + + properties.Property(fmt.Sprintf("vector scalar multiplication %d - %s", size, gp.label), prop.ForAll( + scalarMulVector, + gp.g1, + genElement(), + )) + + properties.Property(fmt.Sprintf("vector sum %d - %s", size, gp.label), prop.ForAll( + sumVector, + gp.g1, + )) + + properties.Property(fmt.Sprintf("vector inner product %d - %s", size, gp.label), prop.ForAll( + innerProductVector, + gp.g1, + gp.g2, + )) + + properties.Property(fmt.Sprintf("vector multiplication %d - %s", size, gp.label), prop.ForAll( + mulVector, + gp.g1, + gp.g2, + )) + } + } + + properties.TestingRun(t, gopter.NewFormatedReporter(false, 260, os.Stdout)) +} + +func BenchmarkVectorOps(b *testing.B) { + // note; to benchmark against "no asm" version, use the following + // build tag: -tags purego + const N = 1 << 24 + a1 := make(Vector, N) + b1 := make(Vector, N) + c1 := make(Vector, N) + var mixer Element + mixer.SetRandom() + for i := 1; i < N; i++ { + a1[i-1].SetUint64(uint64(i)). + Mul(&a1[i-1], &mixer) + b1[i-1].SetUint64(^uint64(i)). + Mul(&b1[i-1], &mixer) + } + + for n := 1 << 4; n <= N; n <<= 1 { + b.Run(fmt.Sprintf("add %d", n), func(b *testing.B) { + _a := a1[:n] + _b := b1[:n] + _c := c1[:n] + b.ResetTimer() + for i := 0; i < b.N; i++ { + _c.Add(_a, _b) + } + }) + + b.Run(fmt.Sprintf("sub %d", n), func(b *testing.B) { + _a := a1[:n] + _b := b1[:n] + _c := c1[:n] + b.ResetTimer() + for i := 0; i < b.N; i++ { + _c.Sub(_a, _b) + } + }) + + b.Run(fmt.Sprintf("scalarMul %d", n), func(b *testing.B) { + _a := a1[:n] + _c := c1[:n] + b.ResetTimer() + for i := 0; i < b.N; i++ { + _c.ScalarMul(_a, &mixer) + } + }) + + b.Run(fmt.Sprintf("sum %d", n), func(b *testing.B) { + _a := a1[:n] + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = _a.Sum() + } + }) + + b.Run(fmt.Sprintf("innerProduct %d", n), func(b *testing.B) { + _a := a1[:n] + _b := b1[:n] + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = _a.InnerProduct(_b) + } + }) + + b.Run(fmt.Sprintf("mul %d", n), func(b *testing.B) { + _a := a1[:n] + _b := b1[:n] + _c := c1[:n] + b.ResetTimer() + for i := 0; i < b.N; i++ { + _c.Mul(_a, _b) + } + }) + } +} + +func genZeroVector(size int) gopter.Gen { + return func(genParams *gopter.GenParameters) *gopter.GenResult { + g := make(Vector, size) + genResult := gopter.NewGenResult(g, gopter.NoShrinker) + return genResult + } +} + +func genMaxVector(size int) gopter.Gen { + return func(genParams *gopter.GenParameters) *gopter.GenResult { + g := make(Vector, size) + + qMinusOne := qElement + qMinusOne[0]-- + + for i := 0; i < size; i++ { + g[i] = qMinusOne + } + genResult := gopter.NewGenResult(g, gopter.NoShrinker) + return genResult + } +} + +func genVector(size int) gopter.Gen { + return func(genParams *gopter.GenParameters) *gopter.GenResult { + g := make(Vector, size) + mixer := Element{ + genParams.NextUint64(), + } + if qElement[0] != ^uint64(0) { + mixer[0] %= (qElement[0] + 1) + } + + for !mixer.smallerThanModulus() { + mixer = Element{ + genParams.NextUint64(), + } + if qElement[0] != ^uint64(0) { + mixer[0] %= (qElement[0] + 1) + } + } + + for i := 1; i <= size; i++ { + g[i-1].SetUint64(uint64(i)). + Mul(&g[i-1], &mixer) + } + + genResult := gopter.NewGenResult(g, gopter.NoShrinker) + return genResult + } +}