github.com/consensys/gnark-crypto@v0.14.0/ecc/bn254/fr/sis/sis.sage (about) 1 ## "sage sis.sage" will generate test_cases.json 2 ## tested with a fresh sage install on macOS (Feb 2023) 3 4 import json 5 6 # BN254 Fr 7 r = 21888242871839275222246405745257275088548364400416034343698204186575808495617 8 frByteSize = 32 9 countToDeath = int(5) 10 gfr = GF(r) 11 Fr = GF(r) 12 Fr.<x> = Fr[] 13 rz = IntegerRing() 14 15 # Montgomery constant 16 rr = Fr(2**256) 17 18 # utils 19 20 21 def buildPoly(a): 22 """ Builds a poly from the array a 23 24 Args: 25 a an array 26 27 Returns: 28 a[0]+a[1]*X + .. + a[n]*X**n 29 """ 30 31 res = Fr(0) 32 for i, v in enumerate(a): 33 res += Fr(v)*x**i 34 return res 35 36 37 def bitAt(i, b): 38 """ 39 Args: 40 i: index of the bit to retrieve 41 b: array of bytes 42 43 Returns: 44 the i-th bit of b, when it is written b[0] || b[1] || ... 45 """ 46 k = i//8 47 if k >= len(b): 48 return 0 49 j = i % 8 50 return (b[k] >> (7-j)) & 1 51 52 53 def toBytes(m, s): 54 """ 55 56 Args: 57 m: a bit int 58 s: the expected number of bytes of the result. If s is bigger than the 59 number of bytes in m, the remaining bytes are set to zero. 60 61 Returns: 62 the byte representation of m as a byte array, as 63 in gnark-crypto. 64 """ 65 _m = rz(m) 66 res = s*[0] 67 mask = 255 68 for i in range(s): 69 res[s-1-i] = _m & 255 70 _m = _m >> 8 71 return res 72 73 74 def splitCoeffs(b, logTwoBound): 75 """ 76 Args: 77 b: an array of bytes 78 logTwoBound: number of bits of the bound 79 80 Returns: 81 an array of coeffs, each coeff being the i-th chunk of logTwoBounds bits of b. 82 The coeffs are formed as follow. The input byte string is implicitly parsed as 83 a slice of field elements of 32 bytes each in bigendian-natural form. the outputs 84 are in a little-endian form. That is, each chunk of size 256 / logTwoBounds of the 85 output can be seen as a polynomial, such that, when evaluated at 2 we get the original 86 field element. 87 """ 88 nbBits = len(b)*8 89 res = [] 90 i = 0 91 92 if len(b) % frByteSize != 0: 93 exit("the length of b should divide the field size") 94 95 # The number of fields that we are parsing. In case we have that 96 # logTwoBound does not divide the number of bits to represent a 97 # field element, we do not merge them. 98 nbField = len(b) / 32 99 nbBitsInField = int(frByteSize * 8) 100 101 for fieldID in range(nbField): 102 fieldStart = fieldID * 256 103 e = 0 104 for bitInField in range(nbBitsInField): 105 j = bitInField % logTwoBound 106 at = fieldStart + nbBitsInField - 1 - bitInField 107 e |= bitAt(at, b) << j 108 # Switch to a new limb 109 if j == logTwoBound - 1 or bitInField == frByteSize * 8 - 1: 110 res.append(e) 111 e = 0 112 113 # careful Montgomery constant... 114 return [Fr(e)*rr**-1 for e in res] 115 116 117 def polyRand(seed, n): 118 """ Generates a pseudo random polynomial of size n from seed. 119 120 Args: 121 seed: seed for the pseudo random gen 122 n: degree of the polynomial 123 """ 124 seed = gfr(seed) 125 a = n*[0] 126 for i in range(n): 127 a[i] = seed**2 128 seed = a[i] 129 return buildPoly(a) 130 131 132 # SIS 133 class SIS: 134 def __init__(self, seed, logTwoDegree, logTwoBound, maxNbElementsToHash): 135 """ 136 Args: 137 seed 138 logTwoDegree: 139 logTwoBound: bound of SIS 140 maxNbElementsToHash 141 """ 142 capacity = maxNbElementsToHash * frByteSize 143 degree = 1 << logTwoDegree 144 145 n = capacity * 8 / logTwoBound # number of coefficients 146 if n % degree == 0: # check how sage / python rounds the int div. 147 n = n / degree 148 else: 149 n = n / degree 150 n = n + 1 151 152 n = int(n) 153 154 self.logTwoBound = logTwoBound 155 self.degree = degree 156 self.size = n 157 self.key = n * [0] 158 for i in range(n): 159 self.key[i] = polyRand(seed, self.degree) 160 seed += 1 161 162 def hash(self, inputs): 163 """ 164 Args: 165 inputs is a vector of Fr elements 166 167 Returns: 168 the sis hash of m. 169 """ 170 b = [] 171 for i in inputs: 172 b.extend(toBytes(i, 32)) 173 174 return self.hash_bytes(b) 175 176 def hash_bytes(self, b): 177 """ 178 Args: 179 b is a list of bytes to hash 180 181 Returns: 182 the sis hash of m. 183 """ 184 # step 1: build the polynomials from m 185 c = splitCoeffs(b, self.logTwoBound) 186 mp = [buildPoly(c[self.degree*i:self.degree*(i+1)]) 187 for i in range(self.size)] 188 189 # step 2: compute sum_i mp[i]*key[i] mod X^n+1 190 modulo = x**self.degree+1 191 res = 0 192 for i in range(self.size): 193 res += self.key[i]*mp[i] 194 res = res % modulo 195 return res 196 197 198 def vectorToString(v): 199 # v is a vector of field elements 200 # we return a list of strings in base10 201 r = [] 202 for e in v: 203 r.append("0x"+rz(e).hex()) 204 return r 205 206 207 def SISParams(seed, logTwoDegree, logTwoBound, maxNbElementsToHash): 208 p = {} 209 p['seed'] = int(seed) 210 p['logTwoDegree'] = int(logTwoDegree) 211 p['logTwoBound'] = int(logTwoBound) 212 p['maxNbElementsToHash'] = int(maxNbElementsToHash) 213 return p 214 215 params = [ 216 SISParams(5, 2, 3, 10), 217 SISParams(5, 4, 3, 10), 218 SISParams(5, 4, 4, 10), 219 SISParams(5, 5, 4, 10), 220 SISParams(5, 6, 5, 10), 221 # SISParams(5, 8, 6, 10), 222 SISParams(5, 10, 6, 10), 223 SISParams(5, 11, 7, 10), 224 SISParams(5, 12, 7, 10), 225 ] 226 227 inputs = [ 228 [Fr(21888242871839275222246405745257275088548364400416034343698204186575808495614)], 229 [Fr(1)], 230 [Fr(42),Fr(8000)], 231 [Fr(1),Fr(2), Fr(0),Fr(21888242871839275222246405745257275088548364400416034343698204186575808495616)], 232 [Fr(1), Fr(0)], 233 [Fr(0), Fr(1)], 234 [Fr(0)], 235 [Fr(0),Fr(0),Fr(0),Fr(0)], 236 [Fr(0),Fr(0),Fr(8000),Fr(0)], 237 ] 238 239 # sprinkle some random elements 240 for i in range(10): 241 line = [] 242 for j in range(i): 243 line.append(gfr.random_element()) 244 inputs.append(line) 245 246 testCases = {} 247 testCases['inputs'] = [] 248 testCases['entries'] = [] 249 250 251 for i, v in enumerate(inputs): 252 testCases['inputs'].append(vectorToString(v)) 253 254 255 for p in params: 256 entry = {} 257 entry['params'] = p 258 entry['expected'] = [] 259 260 print("generating test cases with SIS params " + json.dumps(p)) 261 instance = SIS(p['seed'], p['logTwoDegree'], p['logTwoBound'], p['maxNbElementsToHash']) 262 for i, v in enumerate(inputs): 263 # hash the vector 264 hResult = instance.hash(v) 265 entry['expected'].append(vectorToString(hResult)) 266 267 testCases['entries'].append(entry) 268 269 270 testCases_json = json.dumps(testCases, indent=4) 271 with open("test_cases.json", "w") as outfile: 272 outfile.write(testCases_json)