github.com/consensys/gnark-crypto@v0.14.0/ecc/bn254/fr/tensor-commitment/commitment_test.go (about)

     1  // Copyright 2020 ConsenSys Software Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package tensorcommitment
    16  
    17  import (
    18  	"bytes"
    19  	"hash"
    20  	"math/big"
    21  	"math/bits"
    22  	"strconv"
    23  	"testing"
    24  
    25  	"github.com/consensys/gnark-crypto/ecc/bn254/fr"
    26  	"github.com/consensys/gnark-crypto/ecc/bn254/fr/sis"
    27  	"github.com/stretchr/testify/require"
    28  )
    29  
    30  type DummyHash uint
    31  
    32  func (d DummyHash) Write(p []byte) (n int, err error) {
    33  	return 0, nil
    34  }
    35  
    36  func (d DummyHash) Sum(b []byte) []byte {
    37  	return b
    38  }
    39  
    40  func (d DummyHash) Reset() {}
    41  
    42  func (d DummyHash) Size() int {
    43  	return 0
    44  }
    45  
    46  func (d DummyHash) BlockSize() int {
    47  	return 0
    48  }
    49  
    50  func DummyHashMaker() hash.Hash {
    51  	var res DummyHash
    52  	return &res
    53  }
    54  
    55  func TestAppend(t *testing.T) {
    56  	if bits.UintSize == 32 {
    57  		t.Skip("skipping this test in 32bit.")
    58  	}
    59  
    60  	assert := require.New(t)
    61  
    62  	// tensor commitment
    63  	const (
    64  		rho       = 4
    65  		nbRows    = 10
    66  		nbColumns = 16
    67  	)
    68  	params, err := NewTCParams(rho, nbColumns, nbRows, DummyHashMaker)
    69  	assert.NoError(err)
    70  
    71  	tc := NewTensorCommitment(params)
    72  
    73  	{
    74  		// random Polynomial of size nbRows
    75  		p := make([]fr.Element, nbRows)
    76  		for i := 0; i < nbRows; i++ {
    77  			p[i].SetRandom()
    78  		}
    79  		_, err := tc.Append(p)
    80  		assert.NoError(err)
    81  
    82  		// check if p corresponds to the first column of the state
    83  		for i := 0; i < nbRows; i++ {
    84  			assert.True(tc.State[i][0].Equal(&p[i]), "a column is not filled correctly")
    85  		}
    86  
    87  	}
    88  
    89  	// after a first polynomial has been filled
    90  	{
    91  		// random Polynomial of size nbRows
    92  		p := make([]fr.Element, nbRows)
    93  		for i := 0; i < nbRows; i++ {
    94  			p[i].SetRandom()
    95  		}
    96  		_, err := tc.Append(p)
    97  		assert.NoError(err)
    98  
    99  		// check if p corresponds to the second column of the state
   100  		for i := 0; i < nbRows; i++ {
   101  			assert.True(tc.State[i][1].Equal(&p[i]), "a column is not filled correctly")
   102  		}
   103  	}
   104  
   105  	// polynomial whose size is not a multiple of nbRows
   106  	{
   107  		// random Polynomial of size nbRows
   108  		offset := 4
   109  		p := make([]fr.Element, nbRows+offset)
   110  		for i := 0; i < nbRows+offset; i++ {
   111  			p[i].SetRandom()
   112  		}
   113  		_, err := tc.Append(p)
   114  		assert.NoError(err)
   115  
   116  		// check if p corresponds to the first column of the state
   117  		for i := 0; i < nbRows; i++ {
   118  			assert.True(tc.State[i][2].Equal(&p[i]), "a column is not filled correctly")
   119  		}
   120  		for i := 0; i < offset; i++ {
   121  			assert.True(tc.State[i][3].Equal(&p[i+nbRows]), "a column is not filled correctly")
   122  		}
   123  	}
   124  
   125  	// same to see if the last column was correctly offset
   126  	{
   127  		// random Polynomial of size nbRows
   128  		offset := 4
   129  		p := make([]fr.Element, nbRows+offset)
   130  		for i := 0; i < nbRows+offset; i++ {
   131  			p[i].SetRandom()
   132  		}
   133  		_, err := tc.Append(p)
   134  		assert.NoError(err)
   135  
   136  		// check if p corresponds to the first column of the state
   137  		for i := 0; i < nbRows; i++ {
   138  			assert.True(tc.State[i][4].Equal(&p[i]), "a column is not filled correctly")
   139  		}
   140  		for i := 0; i < offset; i++ {
   141  			assert.True(tc.State[i][5].Equal(&p[i+nbRows]), "a column is not filled correctly")
   142  		}
   143  	}
   144  
   145  }
   146  
   147  func TestLinearCombination(t *testing.T) {
   148  
   149  	rho := 4
   150  	nbRows := 8
   151  	nbColumns := 8
   152  	params, err := NewTCParams(rho, nbColumns, nbRows, DummyHashMaker)
   153  	if err != nil {
   154  		t.Fatal(err)
   155  	}
   156  	tc := NewTensorCommitment(params)
   157  
   158  	// build a random polynomial
   159  	p := make([]fr.Element, nbRows*nbColumns)
   160  	for i := 0; i < 64; i++ {
   161  		p[i].SetRandom()
   162  	}
   163  
   164  	// we select all the entries for the test
   165  	entryList := make([]int, rho*nbColumns)
   166  	for i := 0; i < rho*nbColumns; i++ {
   167  		entryList[i] = i
   168  	}
   169  
   170  	// append p and commit (otherwise the proof cannot be built)
   171  	tc.Append(p)
   172  	_, err = tc.Commit()
   173  	if err != nil {
   174  		t.Fatal(err)
   175  	}
   176  
   177  	// at each trial, it's the i-th line which is selected
   178  	for i := 0; i < nbRows; i++ {
   179  
   180  		// used for the random linear combination.
   181  		// it will act as a selector for the test: it selects the i-th
   182  		// row of p, when p is written as a matrix M_ij, where M_ij=p[i*m+j].
   183  		// The i-th entry of l is 1, the others are 0.
   184  		l := make([]fr.Element, nbRows)
   185  		l[i].SetInt64(1)
   186  
   187  		proof, err := tc.BuildProofAtOnceForTest(l, entryList)
   188  		if err != nil {
   189  			t.Fatal(err)
   190  		}
   191  
   192  		// the i-th line of p is the one that is supposed to be selected
   193  		// (corresponding to the linear combination)
   194  		expected := make([]fr.Element, nbColumns)
   195  		for j := 0; j < nbColumns; j++ {
   196  			expected[j].Set(&p[j*nbRows+i])
   197  		}
   198  
   199  		for j := 0; j < nbColumns; j++ {
   200  			if !expected[j].Equal(&proof.LinearCombination[j]) {
   201  				t.Fatal("expected linear combination is incorrect")
   202  			}
   203  		}
   204  
   205  	}
   206  }
   207  
   208  // Test the verification of a correct proof using a mock hash
   209  func TestCommitmentDummyHash(t *testing.T) {
   210  
   211  	var rho, nbColumns, nbRows int
   212  	rho = 4
   213  	nbColumns = 8
   214  	nbRows = 8
   215  
   216  	var h DummyHash
   217  	params, err := NewTCParams(rho, nbColumns, nbRows, DummyHashMaker)
   218  	if err != nil {
   219  		t.Fatal(err)
   220  	}
   221  	tc := NewTensorCommitment(params)
   222  
   223  	// random polynomial
   224  	p := make([]fr.Element, nbRows*nbColumns)
   225  	for i := 0; i < nbRows*nbColumns; i++ {
   226  		p[i].SetRandom()
   227  	}
   228  
   229  	// coefficients for the linear combination
   230  	l := make([]fr.Element, nbRows)
   231  	for i := 0; i < nbRows; i++ {
   232  		l[i].SetRandom()
   233  	}
   234  
   235  	// we select all the entries for the test
   236  	entryList := make([]int, rho*nbColumns)
   237  	for i := 0; i < rho*nbColumns; i++ {
   238  		entryList[i] = i
   239  	}
   240  
   241  	// compute the digest...
   242  	_, err = tc.Append(p)
   243  	if err != nil {
   244  		t.Fatal(err)
   245  	}
   246  	digest, err := tc.Commit()
   247  	if err != nil {
   248  		t.Fatal(err)
   249  	}
   250  
   251  	// build the proof...
   252  	proof, err := tc.BuildProofAtOnceForTest(l, entryList)
   253  	if err != nil {
   254  		t.Fatal(err)
   255  	}
   256  
   257  	// verify that the proof is correct
   258  	err = Verify(proof, digest, l, h)
   259  	if err != nil {
   260  		t.Fatal(err)
   261  	}
   262  
   263  }
   264  
   265  // Test the opening using a dummy hash
   266  func TestOpeningDummyHash(t *testing.T) {
   267  
   268  	var rho, nbColumns, nbRows int
   269  	rho = 4
   270  	nbColumns = 8
   271  	nbRows = 8
   272  
   273  	params, err := NewTCParams(rho, nbColumns, nbRows, DummyHashMaker)
   274  	if err != nil {
   275  		t.Fatal(err)
   276  	}
   277  	tc := NewTensorCommitment(params)
   278  
   279  	// random polynomial
   280  	p := make([]fr.Element, nbColumns*nbRows)
   281  	for i := 0; i < nbColumns*nbRows; i++ {
   282  		p[i].SetRandom()
   283  	}
   284  
   285  	// the coefficients are (1,x,x^2,..,x^{n-1}) where x is the point
   286  	// at which the opening is done
   287  	var xm, x fr.Element
   288  	x.SetRandom()
   289  	hi := make([]fr.Element, nbColumns) // stores [1,x^{nbRows},..,x^{nbRows*nbColumns^-1}]
   290  	lo := make([]fr.Element, nbRows)    // stores [1,x,..,x^{nbRows-1}]
   291  	lo[0].SetInt64(1)
   292  	hi[0].SetInt64(1)
   293  	xm.Exp(x, big.NewInt(int64(nbRows)))
   294  	for i := 1; i < nbColumns; i++ {
   295  		lo[i].Mul(&lo[i-1], &x)
   296  		hi[i].Mul(&hi[i-1], &xm)
   297  	}
   298  
   299  	// create the digest before computing the proof
   300  	_, err = tc.Append(p)
   301  	if err != nil {
   302  		t.Fatal(err)
   303  	}
   304  	_, err = tc.Commit()
   305  	if err != nil {
   306  		t.Fatal(err)
   307  	}
   308  
   309  	// build the proof
   310  	entryList := make([]int, rho*nbColumns)
   311  	for i := 0; i < rho*nbColumns; i++ {
   312  		entryList[i] = i
   313  	}
   314  	proof, err := tc.BuildProofAtOnceForTest(lo, entryList)
   315  	if err != nil {
   316  		t.Fatal(err)
   317  	}
   318  
   319  	// finish the evaluation by computing
   320  	// [linearCombination] * [hi]^t
   321  	var eval, tmp fr.Element
   322  	for i := 0; i < nbColumns; i++ {
   323  		tmp.Mul(&proof.LinearCombination[i], &hi[i])
   324  		eval.Add(&eval, &tmp)
   325  	}
   326  
   327  	// compute the real evaluation of p at x manually
   328  	var expectedEval fr.Element
   329  	for i := 0; i < nbRows*nbColumns; i++ {
   330  		expectedEval.Mul(&expectedEval, &x)
   331  		expectedEval.Add(&expectedEval, &p[len(p)-i-1])
   332  	}
   333  
   334  	// the results coincide
   335  	if !expectedEval.Equal(&eval) {
   336  		t.Fatal("p(x) != [ lo ] x M x [ hi ]^t")
   337  	}
   338  
   339  }
   340  
   341  // Check the commitments are correctly formed when appending a polynomial
   342  func TestAppendSis(t *testing.T) {
   343  	if bits.UintSize == 32 {
   344  		t.Skip("skipping this test in 32bit.")
   345  	}
   346  	const (
   347  		rho          = 4
   348  		nbColumns    = 8
   349  		nbRows       = 8
   350  		logTwoDegree = 1
   351  		logTwoBound  = 4
   352  	)
   353  
   354  	assert := require.New(t)
   355  
   356  	// keySize := 256
   357  	hMaker, err := sis.NewRingSISMaker(5, logTwoDegree, logTwoBound, 8)
   358  	assert.NoError(err)
   359  
   360  	params, err := NewTCParams(rho, nbColumns, nbRows, hMaker)
   361  	assert.NoError(err)
   362  
   363  	tc := NewTensorCommitment(params)
   364  
   365  	// random polynomial (that does not fill the full matrix)
   366  	offset := 4
   367  	p := make([]fr.Element, nbRows*nbColumns-offset)
   368  	for i := 0; i < nbRows*nbColumns-offset; i++ {
   369  		p[i].SetRandom()
   370  	}
   371  
   372  	s, err := tc.Append(p)
   373  	assert.NoError(err)
   374  
   375  	assert.Equal(nbColumns, len(s))
   376  
   377  	// check the hashes of the columns
   378  	h := hMaker()
   379  	for i := 0; i < nbColumns-1; i++ {
   380  		h.Reset()
   381  		for j := 0; j < nbRows; j++ {
   382  			h.Write(p[i*nbRows+j].Marshal())
   383  		}
   384  		_s := h.Sum(nil)
   385  		assert.True(bytes.Equal(_s, s[i]), "error hash column when appending a polynomial for column", i)
   386  	}
   387  
   388  	// last column
   389  	h.Reset()
   390  	for i := (nbColumns - 1) * nbRows; i < nbColumns*nbRows-offset; i++ {
   391  		h.Write(p[i].Marshal())
   392  	}
   393  	var tmp fr.Element
   394  	for i := nbColumns*nbRows - offset; i < nbColumns*nbRows; i++ {
   395  		h.Write(tmp.Marshal())
   396  	}
   397  	_s := h.Sum(nil)
   398  	assert.True(bytes.Equal(_s, s[nbColumns-1]), "error hash column when appending a polynomial")
   399  }
   400  
   401  // Test the verification of a correct proof using SIS as hash
   402  func TestCommitmentSis(t *testing.T) {
   403  	if bits.UintSize == 32 {
   404  		t.Skip("skipping this test in 32bit.")
   405  	}
   406  	var rho, nbColumns, nbRows int
   407  	rho = 4
   408  	nbColumns = 8
   409  	nbRows = 8
   410  
   411  	logTwoDegree := 1
   412  	logTwoBound := 4
   413  	hMaker, err := sis.NewRingSISMaker(5, logTwoDegree, logTwoBound, 8)
   414  	if err != nil {
   415  		t.Fatal(err)
   416  	}
   417  
   418  	params, err := NewTCParams(rho, nbColumns, nbRows, hMaker)
   419  	if err != nil {
   420  		t.Fatal(err)
   421  	}
   422  	tc := NewTensorCommitment(params)
   423  
   424  	// random polynomial
   425  	p := make([]fr.Element, nbRows*nbColumns)
   426  	for i := 0; i < nbRows*nbColumns; i++ {
   427  		p[i].SetRandom()
   428  	}
   429  
   430  	// coefficients for the linear combination
   431  	l := make([]fr.Element, nbRows)
   432  	for i := 0; i < nbRows; i++ {
   433  		l[i].SetRandom()
   434  	}
   435  
   436  	// compute the digest...
   437  	_, err = tc.Append(p)
   438  	if err != nil {
   439  		t.Fatal(err)
   440  	}
   441  	digest, err := tc.Commit()
   442  	if err != nil {
   443  		t.Fatal(err)
   444  	}
   445  
   446  	// test 1: we select all the entries
   447  	{
   448  		entryList := make([]int, rho*nbColumns)
   449  		for i := 0; i < rho*nbColumns; i++ {
   450  			entryList[i] = i
   451  		}
   452  
   453  		// build the proof...
   454  		proof, err := tc.BuildProofAtOnceForTest(l, entryList)
   455  		if err != nil {
   456  			t.Fatal(err)
   457  		}
   458  
   459  		// verify that the proof is correct
   460  		err = Verify(proof, digest, l, hMaker())
   461  		if err != nil {
   462  			t.Fatal(err)
   463  		}
   464  	}
   465  	// test 2: we select a subset of the entries
   466  	{
   467  
   468  		entryList := make([]int, 2)
   469  		entryList[0] = 1
   470  		entryList[1] = 4
   471  
   472  		// build the proof...
   473  		proof, err := tc.BuildProofAtOnceForTest(l, entryList)
   474  		if err != nil {
   475  			t.Fatal(err)
   476  		}
   477  
   478  		// verify that the proof is correct
   479  		err = Verify(proof, digest, l, hMaker())
   480  		if err != nil {
   481  			t.Fatal(err)
   482  		}
   483  	}
   484  }
   485  
   486  // benches
   487  func BenchmarkTensorCommitment(b *testing.B) {
   488  
   489  	// prepare the tensor commitment
   490  	logTwoDegree := 4
   491  	logTwoBound := 4
   492  	rho := 4
   493  
   494  	for i := 0; i < 6; i++ {
   495  
   496  		nbColumns := (1 << (3 + i))
   497  		nbRows := nbColumns
   498  
   499  		h, _ := sis.NewRingSISMaker(5, logTwoDegree, logTwoBound, nbRows)
   500  		params, _ := NewTCParams(rho, nbColumns, nbRows, h)
   501  		tc := NewTensorCommitment(params)
   502  
   503  		// random polynomial
   504  		p := make([]fr.Element, nbRows*nbColumns)
   505  		for i := 0; i < nbRows*nbColumns; i++ {
   506  			p[i].SetRandom()
   507  		}
   508  
   509  		// run the benchmark
   510  		b.Run("size poly"+strconv.Itoa(nbRows*nbColumns), func(b *testing.B) {
   511  			b.ResetTimer()
   512  			for i := 0; i < b.N; i++ {
   513  				tc.Append(p)
   514  				tc.Commit()
   515  			}
   516  		})
   517  
   518  	}
   519  
   520  }