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)