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 }