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

     1  // Copyright 2023 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 sis
    16  
    17  import (
    18  	"bytes"
    19  	"encoding/binary"
    20  	"errors"
    21  	"hash"
    22  	"math/big"
    23  	"math/bits"
    24  
    25  	"github.com/bits-and-blooms/bitset"
    26  	"github.com/consensys/gnark-crypto/ecc/bn254/fr"
    27  	"github.com/consensys/gnark-crypto/ecc/bn254/fr/fft"
    28  	"github.com/consensys/gnark-crypto/internal/parallel"
    29  	"golang.org/x/crypto/blake2b"
    30  )
    31  
    32  var (
    33  	ErrNotAPowerOfTwo = errors.New("d must be a power of 2")
    34  )
    35  
    36  // Ring-SIS instance
    37  type RSis struct {
    38  
    39  	// buffer storing the data to hash
    40  	buffer bytes.Buffer
    41  
    42  	// Vectors in ℤ_{p}/Xⁿ+1
    43  	// A[i] is the i-th polynomial.
    44  	// Ag the evaluation form of the polynomials in A on the coset √(g) * <g>
    45  	A  [][]fr.Element
    46  	Ag [][]fr.Element
    47  
    48  	// LogTwoBound (Infinity norm) of the vector to hash. It means that each component in m
    49  	// is < 2^B, where m is the vector to hash (the hash being A*m).
    50  	// cf https://hackmd.io/7OODKWQZRRW9RxM5BaXtIw , B >= 3.
    51  	LogTwoBound int
    52  
    53  	// domain for the polynomial multiplication
    54  	Domain        *fft.Domain
    55  	twiddleCosets []fr.Element // see FFT64 and precomputeTwiddlesCoset
    56  
    57  	// d, the degree of X^{d}+1
    58  	Degree int
    59  
    60  	// in bytes, represents the maximum number of bytes the .Write(...) will handle;
    61  	// ( maximum number of bytes to sum )
    62  	capacity            int
    63  	maxNbElementsToHash int
    64  
    65  	// allocate memory once per instance (used in Sum())
    66  	bufM, bufRes fr.Vector
    67  	bufMValues   *bitset.BitSet
    68  }
    69  
    70  // NewRSis creates an instance of RSis.
    71  // seed: seed for the randomness for generating A.
    72  // logTwoDegree: if d := logTwoDegree, the ring will be ℤ_{p}[X]/Xᵈ-1, where X^{2ᵈ} is the 2ᵈ⁺¹-th cyclotomic polynomial
    73  // logTwoBound: the bound of the vector to hash (using the infinity norm).
    74  // maxNbElementsToHash: maximum number of field elements the instance handles
    75  // used to derived n, the number of polynomials in A, and max size of instance's internal buffer.
    76  func NewRSis(seed int64, logTwoDegree, logTwoBound, maxNbElementsToHash int) (*RSis, error) {
    77  
    78  	if logTwoBound > 64 {
    79  		return nil, errors.New("logTwoBound too large")
    80  	}
    81  	if bits.UintSize == 32 {
    82  		return nil, errors.New("unsupported architecture; need 64bit target")
    83  	}
    84  
    85  	degree := 1 << logTwoDegree
    86  	capacity := maxNbElementsToHash * fr.Bytes
    87  
    88  	// n: number of polynomials in A
    89  	// len(m) == degree * n
    90  	// with each element in m being logTwoBounds bits from the instance buffer.
    91  	// that is, to fill m, we need [degree * n * logTwoBound] bits of data
    92  	// capacity == [degree * n * logTwoBound] / 8
    93  	// n == (capacity*8)/(degree*logTwoBound)
    94  
    95  	// First n <- #limbs to represent a single field element
    96  	n := (fr.Bytes * 8) / logTwoBound
    97  	if n*logTwoBound < fr.Bytes*8 {
    98  		n++
    99  	}
   100  
   101  	// Then multiply by the number of field elements
   102  	n *= maxNbElementsToHash
   103  
   104  	// And divide (+ ceil) to get the number of polynomials
   105  	if n%degree == 0 {
   106  		n /= degree
   107  	} else {
   108  		n /= degree // number of polynomials
   109  		n++
   110  	}
   111  
   112  	// domains (shift is √{gen} )
   113  	var shift fr.Element
   114  	shift.SetString("19103219067921713944291392827692070036145651957329286315305642004821462161904") // -> 2²⁸-th root of unity of bn254
   115  	e := int64(1 << (28 - (logTwoDegree + 1)))
   116  	shift.Exp(shift, big.NewInt(e))
   117  
   118  	r := &RSis{
   119  		LogTwoBound:         logTwoBound,
   120  		capacity:            capacity,
   121  		Degree:              degree,
   122  		Domain:              fft.NewDomain(uint64(degree), fft.WithShift(shift)),
   123  		A:                   make([][]fr.Element, n),
   124  		Ag:                  make([][]fr.Element, n),
   125  		bufM:                make(fr.Vector, degree*n),
   126  		bufRes:              make(fr.Vector, degree),
   127  		bufMValues:          bitset.New(uint(n)),
   128  		maxNbElementsToHash: maxNbElementsToHash,
   129  	}
   130  	if r.LogTwoBound == 8 && r.Degree == 64 {
   131  		// TODO @gbotrel fixme, that's dirty.
   132  		r.twiddleCosets = PrecomputeTwiddlesCoset(r.Domain.Generator, r.Domain.FrMultiplicativeGen)
   133  	}
   134  
   135  	// filling A
   136  	a := make([]fr.Element, n*r.Degree)
   137  	ag := make([]fr.Element, n*r.Degree)
   138  
   139  	parallel.Execute(n, func(start, end int) {
   140  		var buf bytes.Buffer
   141  		for i := start; i < end; i++ {
   142  			rstart, rend := i*r.Degree, (i+1)*r.Degree
   143  			r.A[i] = a[rstart:rend:rend]
   144  			r.Ag[i] = ag[rstart:rend:rend]
   145  			for j := 0; j < r.Degree; j++ {
   146  				r.A[i][j] = genRandom(seed, int64(i), int64(j), &buf)
   147  			}
   148  
   149  			// fill Ag the evaluation form of the polynomials in A on the coset √(g) * <g>
   150  			copy(r.Ag[i], r.A[i])
   151  			r.Domain.FFT(r.Ag[i], fft.DIF, fft.OnCoset())
   152  		}
   153  	})
   154  
   155  	return r, nil
   156  }
   157  
   158  func (r *RSis) Write(p []byte) (n int, err error) {
   159  	r.buffer.Write(p)
   160  	return len(p), nil
   161  }
   162  
   163  // Sum appends the current hash to b and returns the resulting slice.
   164  // It does not change the underlying hash state.
   165  // The instance buffer is interpreted as a sequence of coefficients of size r.Bound bits long.
   166  // The function returns the hash of the polynomial as a a sequence []fr.Elements, interpreted as []bytes,
   167  // corresponding to sum_i A[i]*m Mod X^{d}+1
   168  func (r *RSis) Sum(b []byte) []byte {
   169  	buf := r.buffer.Bytes()
   170  	if len(buf) > r.capacity {
   171  		panic("buffer too large")
   172  	}
   173  
   174  	fastPath := r.LogTwoBound == 8 && r.Degree == 64
   175  
   176  	// clear the buffers of the instance.
   177  	defer r.cleanupBuffers()
   178  
   179  	m := r.bufM
   180  	mValues := r.bufMValues
   181  
   182  	if fastPath {
   183  		// fast path.
   184  		limbDecomposeBytes8_64(buf, m, mValues)
   185  	} else {
   186  		limbDecomposeBytes(buf, m, r.LogTwoBound, r.Degree, mValues)
   187  	}
   188  
   189  	// we can hash now.
   190  	res := r.bufRes
   191  
   192  	// method 1: fft
   193  	for i := 0; i < len(r.Ag); i++ {
   194  		if !mValues.Test(uint(i)) {
   195  			// means m[i*r.Degree : (i+1)*r.Degree] == [0...0]
   196  			// we can skip this, FFT(0) = 0
   197  			continue
   198  		}
   199  		k := m[i*r.Degree : (i+1)*r.Degree]
   200  		if fastPath {
   201  			// fast path.
   202  			FFT64(k, r.twiddleCosets)
   203  		} else {
   204  			r.Domain.FFT(k, fft.DIF, fft.OnCoset(), fft.WithNbTasks(1))
   205  		}
   206  		mulModAcc(res, r.Ag[i], k)
   207  	}
   208  	r.Domain.FFTInverse(res, fft.DIT, fft.OnCoset(), fft.WithNbTasks(1)) // -> reduces mod Xᵈ+1
   209  
   210  	resBytes, err := res.MarshalBinary()
   211  	if err != nil {
   212  		panic(err)
   213  	}
   214  
   215  	return append(b, resBytes[4:]...) // first 4 bytes are uint32(len(res))
   216  }
   217  
   218  // Reset resets the Hash to its initial state.
   219  func (r *RSis) Reset() {
   220  	r.buffer.Reset()
   221  }
   222  
   223  // Size returns the number of bytes Sum will return.
   224  func (r *RSis) Size() int {
   225  
   226  	// The size in bits is the size in bits of a polynomial in A.
   227  	degree := len(r.A[0])
   228  	totalSize := degree * fr.Modulus().BitLen() / 8
   229  
   230  	return totalSize
   231  }
   232  
   233  // BlockSize returns the hash's underlying block size.
   234  // The Write method must be able to accept any amount
   235  // of data, but it may operate more efficiently if all writes
   236  // are a multiple of the block size.
   237  func (r *RSis) BlockSize() int {
   238  	return 0
   239  }
   240  
   241  // Construct a hasher generator. It takes as input the same parameters
   242  // as `NewRingSIS` and outputs a function which returns fresh hasher
   243  // everytime it is called
   244  func NewRingSISMaker(seed int64, logTwoDegree, logTwoBound, maxNbElementsToHash int) (func() hash.Hash, error) {
   245  	return func() hash.Hash {
   246  		h, err := NewRSis(seed, logTwoDegree, logTwoBound, maxNbElementsToHash)
   247  		if err != nil {
   248  			panic(err)
   249  		}
   250  		return h
   251  	}, nil
   252  
   253  }
   254  
   255  func genRandom(seed, i, j int64, buf *bytes.Buffer) fr.Element {
   256  
   257  	buf.Reset()
   258  	buf.WriteString("SIS")
   259  	binary.Write(buf, binary.BigEndian, seed)
   260  	binary.Write(buf, binary.BigEndian, i)
   261  	binary.Write(buf, binary.BigEndian, j)
   262  
   263  	digest := blake2b.Sum256(buf.Bytes())
   264  
   265  	var res fr.Element
   266  	res.SetBytes(digest[:])
   267  
   268  	return res
   269  }
   270  
   271  // mulMod computes p * q in ℤ_{p}[X]/Xᵈ+1.
   272  // Is assumed that pLagrangeShifted and qLagrangeShifted are of the correct sizes
   273  // and that they are in evaluation form on √(g) * <g>
   274  // The result is not FFTinversed. The fft inverse is done once every
   275  // multiplications are done.
   276  func mulMod(pLagrangeCosetBitReversed, qLagrangeCosetBitReversed []fr.Element) []fr.Element {
   277  
   278  	res := make([]fr.Element, len(pLagrangeCosetBitReversed))
   279  	for i := 0; i < len(pLagrangeCosetBitReversed); i++ {
   280  		res[i].Mul(&pLagrangeCosetBitReversed[i], &qLagrangeCosetBitReversed[i])
   281  	}
   282  
   283  	// NOT fft inv for now, wait until every part of the keys have been multiplied
   284  	// r.Domain.FFTInverse(res, fft.DIT, true)
   285  
   286  	return res
   287  
   288  }
   289  
   290  // mulMod + accumulate in res.
   291  func mulModAcc(res []fr.Element, pLagrangeCosetBitReversed, qLagrangeCosetBitReversed []fr.Element) {
   292  	var t fr.Element
   293  	for i := 0; i < len(pLagrangeCosetBitReversed); i++ {
   294  		t.Mul(&pLagrangeCosetBitReversed[i], &qLagrangeCosetBitReversed[i])
   295  		res[i].Add(&res[i], &t)
   296  	}
   297  }
   298  
   299  // Returns a clone of the RSis parameters with a fresh and empty buffer. Does not
   300  // mutate the current instance. The keys and the public parameters of the SIS
   301  // instance are not deep-copied. It is useful when we want to hash in parallel.
   302  // Otherwise, we would have to generate an entire RSis for each thread.
   303  func (r *RSis) CopyWithFreshBuffer() RSis {
   304  	res := *r
   305  	res.buffer = bytes.Buffer{}
   306  	res.bufM = make(fr.Vector, len(r.bufM))
   307  	res.bufMValues = bitset.New(r.bufMValues.Len())
   308  	res.bufRes = make(fr.Vector, len(r.bufRes))
   309  	return res
   310  }
   311  
   312  // Cleanup the buffers of the RSis instance
   313  func (r *RSis) cleanupBuffers() {
   314  	r.bufMValues.ClearAll()
   315  	for i := 0; i < len(r.bufM); i++ {
   316  		r.bufM[i].SetZero()
   317  	}
   318  	for i := 0; i < len(r.bufRes); i++ {
   319  		r.bufRes[i].SetZero()
   320  	}
   321  }
   322  
   323  // Split an slice of bytes representing an array of serialized field element in
   324  // big-endian form into an array of limbs representing the same field elements
   325  // in little-endian form. Namely, if our field is represented with 64 bits and we
   326  // have the following field element 0x0123456789abcdef (0 being the most significant
   327  // character and and f being the least significant one) and our log norm bound is
   328  // 16 (so 1 hex character = 1 limb). The function assigns the values of m to [f, e,
   329  // d, c, b, a, ..., 3, 2, 1, 0]. m should be preallocated and zeroized. Additionally,
   330  // we have the guarantee that 2 bits contributing to different field elements cannot
   331  // be part of the same limb.
   332  func LimbDecomposeBytes(buf []byte, m fr.Vector, logTwoBound int) {
   333  	limbDecomposeBytes(buf, m, logTwoBound, 0, nil)
   334  }
   335  
   336  // Split an slice of bytes representing an array of serialized field element in
   337  // big-endian form into an array of limbs representing the same field elements
   338  // in little-endian form. Namely, if our field is represented with 64 bits and we
   339  // have the following field element 0x0123456789abcdef (0 being the most significant
   340  // character and and f being the least significant one) and our log norm bound is
   341  // 16 (so 1 hex character = 1 limb). The function assigns the values of m to [f, e,
   342  // d, c, b, a, ..., 3, 2, 1, 0]. m should be preallocated and zeroized. mValues is
   343  // an optional bitSet. If provided, it must be empty. The function will set bit "i"
   344  // to indicate the that i-th SIS input polynomial should be non-zero. Recall, that a
   345  // SIS polynomial corresponds to a chunk of limbs of size `degree`. Additionally,
   346  // we have the guarantee that 2 bits contributing to different field elements cannot
   347  // be part of the same limb.
   348  func limbDecomposeBytes(buf []byte, m fr.Vector, logTwoBound, degree int, mValues *bitset.BitSet) {
   349  
   350  	// bitwise decomposition of the buffer, in order to build m (the vector to hash)
   351  	// as a list of polynomials, whose coefficients are less than r.B bits long.
   352  	// Say buf=[0xbe,0x0f]. As a stream of bits it is interpreted like this:
   353  	// 10111110 00001111. BitAt(0)=1 (=leftmost bit), bitAt(1)=0 (=second leftmost bit), etc.
   354  	nbBits := len(buf) * 8
   355  	bitAt := func(i int) uint8 {
   356  		k := i / 8
   357  		if k >= len(buf) {
   358  			return 0
   359  		}
   360  		b := buf[k]
   361  		j := i % 8
   362  		return b >> (7 - j) & 1
   363  	}
   364  
   365  	// we process the input buffer by blocks of r.LogTwoBound bits
   366  	// each of these block (<< 64bits) are interpreted as a coefficient
   367  	mPos := 0
   368  	for fieldStart := 0; fieldStart < nbBits; {
   369  		for bitInField := 0; bitInField < fr.Bytes*8; {
   370  
   371  			j := bitInField % logTwoBound
   372  
   373  			// r.LogTwoBound < 64; we just use the first word of our element here,
   374  			// and set the bits from LSB to MSB.
   375  			at := fieldStart + fr.Bytes*8 - bitInField - 1
   376  
   377  			m[mPos][0] |= uint64(bitAt(at) << j)
   378  			bitInField++
   379  
   380  			// Check if mPos is zero and mark as non-zero in the bitset if not
   381  			if m[mPos][0] != 0 && mValues != nil {
   382  				mValues.Set(uint(mPos / degree))
   383  			}
   384  
   385  			if j == logTwoBound-1 || bitInField == fr.Bytes*8 {
   386  				mPos++
   387  			}
   388  		}
   389  		fieldStart += fr.Bytes * 8
   390  	}
   391  }
   392  
   393  // see limbDecomposeBytes; this function is optimized for the case where
   394  // logTwoBound == 8 and degree == 64
   395  func limbDecomposeBytes8_64(buf []byte, m fr.Vector, mValues *bitset.BitSet) {
   396  	// with logTwoBound == 8, we can actually advance byte per byte.
   397  	const degree = 64
   398  	j := 0
   399  
   400  	for startPos := fr.Bytes - 1; startPos < len(buf); startPos += fr.Bytes {
   401  		for i := startPos; i >= startPos-fr.Bytes+1; i-- {
   402  			m[j][0] = uint64(buf[i])
   403  			if m[j][0] != 0 {
   404  				mValues.Set(uint(j / degree))
   405  			}
   406  			j++
   407  		}
   408  	}
   409  }