plonk_kate.sage 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  1. # We'll use y^2 = x^3 + 3 for our curve, over F_101
  2. p = 101
  3. F = FiniteField(p)
  4. R.<x_f> = F[]
  5. E = EllipticCurve(F, [0, 3])
  6. print(E)
  7. e_points = E.points()
  8. # Our generator G_1 = E(1, 2)
  9. G_1 = e_points[1]
  10. # Let's find a subgroup with this generator.
  11. print(f"Finding a subgroup with generator {G_1} ...")
  12. r = 1
  13. elem = G_1
  14. while True:
  15. elem += G_1
  16. r += 1
  17. if elem == e_points[0]:
  18. break
  19. print(f"Found subgroup of order r={r} using generator {G_1}")
  20. # Now let's find the embedding degree.
  21. # The embedding degree is the smallest k such that r|p^k - 1
  22. # In other words: p^k == 1 mod r
  23. k = 1
  24. print(f"Finding embedding degree for {p}^k mod {r} ...")
  25. while True:
  26. if p ^ k % r == 1:
  27. break
  28. k += 1
  29. print(f"Found embedding degree: k={k}")
  30. # Our extension field. The polynomial x^2+2 is irreducible in F_101.
  31. F2.<u> = F.extension(x_f^2+2, 'u')
  32. assert u^2 == -2
  33. print(F2)
  34. E2 = EllipticCurve(F2, [0, 3])
  35. print(E2)
  36. # One of the generators for this curve we can use is (36, 31u)
  37. G_2 = E2(36, 31*u)
  38. # Now we build the trusted setup. The SRS is a list of EC points
  39. # parameterized by a randomly generated secret number s.
  40. # According to the PLONK protocol paper, a circuit with n gates requires
  41. # an SRS with at least n+5 elements.
  42. # We choose 2 as our random number for demo purposes.
  43. s = 2
  44. # Our circuit will have 4 gates.
  45. n_gates = 4
  46. SRS = []
  47. for i in range(0, n_gates+3):
  48. SRS.append(s^i * G_1)
  49. for i in range(0, 2):
  50. SRS.append(s^i * G_2)
  51. # Composing our circuit. We'll test a^2 + b^2 = c^2:
  52. # x_1 * x_1 = x_2
  53. # x_3 * x_3 = x_4
  54. # x_5 * x_5 = x_6
  55. # x_2 + x_4 = x_6
  56. #
  57. # In order to satisfy these constraints, we need to supply six numbers
  58. # as wire values that make all of the equations correct.
  59. # e.g. x=(3, 9, 4, 16, 5, 25) would work.
  60. # A full PLONK gate looks like this:
  61. # (q_L)*a + (q_R)*b + (q_O)*c + (q_M)*a*b + q_C = 0
  62. #
  63. # Where a, b, c are the left, right, output wires of the gate.
  64. #
  65. # a + b = c ---> q_L=1 , q_R=1, q_O=-1, q_M=0, q_C = 0
  66. # a * b = c ---> q_O=-1, q_M=1, and the rest = 0
  67. #
  68. # To bind a variable to a public value:
  69. # q_R = q_O = q_M = 0
  70. # q_L = 1
  71. # q_C = public_value
  72. #
  73. # Considering all inputs as private, we get these four PLONK gates
  74. # representing our circuit:
  75. # 0*a_1 + 0*b_1 + (-1)*c_1 + 1*a_1*b_1 + 0 = 0 (a_1 * b_1 = c_1)
  76. # 0*a_2 + 0*b_2 + (-1)*c_2 + 1*a_2*b_2 + 0 = 0 (a_2 * b_2 = c_2)
  77. # 0*a_3 + 0*b_3 + (-1)*c_3 + 1*a_3*b_3 + 0 = 0 (a_3 * b_3 = c_3)
  78. # 1*a_4 + 1*b_4 + (-1)*c_4 + 0*a_4*b_4 + 0 = 0 (a_4 + b_4 = c_4)
  79. #
  80. # So let's test with (3, 4, 5)
  81. # a_i (left) values will be (3, 4, 5, 9)
  82. # b_i (right) values will be (3, 4, 5, 16)
  83. # c_i (output) values will be (9, 16, 25, 25)
  84. # Selectors
  85. q_L = vector([0, 0, 0, 1])
  86. q_R = vector([0, 0, 0, 1])
  87. q_O = vector([-1, -1, -1, -1])
  88. q_M = vector([1, 1, 1, 0])
  89. q_C = vector([0, 0, 0, 0])
  90. # Assignments
  91. a = vector([3, 4, 5, 9])
  92. b = vector([3, 4, 5, 16])
  93. c = vector([9, 16, 25, 25])
  94. # Roots of Unity.
  95. # The vectors for our circuit and assignment are all length 4, so the domain
  96. # for our polynomial interpolation must have at least four elements.
  97. roots_of_unity = []
  98. F_r = FiniteField(r)
  99. for i in F_r:
  100. if i^4 == 1:
  101. roots_of_unity.append(i)
  102. omega_0 = roots_of_unity[0]
  103. omega_1 = roots_of_unity[1]
  104. omega_2 = roots_of_unity[3]
  105. omega_3 = roots_of_unity[2]
  106. # Cosets
  107. # k_1 not in H, k_2 not in H nor k_1H
  108. k_1 = 2
  109. k_2 = 3
  110. H = [omega_0, omega_1, omega_2, omega_3]
  111. k1H = [H[0]*k_1, H[1]*k_1, H[2]*k_1, H[3]*k_1]
  112. k2H = [H[0]*k_2, H[1]*k_2, H[2]*k_2, H[3]*k_2]
  113. print("Polynomial interpolation using roots of unity")
  114. print(f"H: {H}")
  115. print(f"k1H: {k1H}")
  116. print(f"k2H: {k2H}")
  117. # Interpolating using the Roots of Unity
  118. # The interpolated polynomial will be degree-3 and have the form:
  119. # f_a(x) = d + c*x + b*x^2 + a*x^3
  120. # f_a(1) = 3, f_a(4) = 4, f_a(16) = 5, f_a(13) = 9
  121. # Note that the above x is H (the omegas)
  122. #
  123. # This gives a system of equations:
  124. # f(1) = d + c*1 + b*1^2 + a*1^3 = 3
  125. # f(4) = d + c*4 + b*4^2 + a*4^3 = 4
  126. # f(16) = d + c*16 + b*16^2 + a*16^3 = 5
  127. # f(13) = d + c*13 + b*13^2 + a*13^3 = 9
  128. # We can rewrite it as a matrix equation and solve by computing
  129. # an inverse matrix.
  130. def inverse_matrix(c):
  131. return Matrix([
  132. [c[0]^0, c[0]^1, c[0]^2, c[0]^3],
  133. [c[1]^0, c[1]^1, c[1]^2, c[1]^3],
  134. [c[2]^0, c[2]^1, c[2]^2, c[2]^3],
  135. [c[3]^0, c[3]^1, c[3]^2, c[3]^3],
  136. ])^-1
  137. # Now we can find polynomial f_a by multiplying the vector a=(3,4,5,9) by
  138. # the interpolation matrix.
  139. f_a_coeffs = inverse_matrix(H) * a
  140. f_b_coeffs = inverse_matrix(H) * b
  141. f_c_coeffs = inverse_matrix(H) * c
  142. q_L_coeffs = inverse_matrix(H) * q_L
  143. q_R_coeffs = inverse_matrix(H) * q_R
  144. q_O_coeffs = inverse_matrix(H) * q_O
  145. q_M_coeffs = inverse_matrix(H) * q_M
  146. q_C_coeffs = inverse_matrix(H) * q_C
  147. # The copy constraints involving left, right, output values are encoded as
  148. # polynomials S_sigma_1, S_sigma_2, S_sigma_3 using the cosets we found
  149. # earlier. The roots of unity H are used to label entries in vector a,
  150. # the elements of k1H are used to label entries in vector b, and vector c is
  151. # labeled by the elements of k2H.
  152. print("Copy constraints:")
  153. print(f"a: {a}")
  154. print(f"b: {b}")
  155. print(f"c: {c}")
  156. # a1 = b1, a2 = b2, a3 = b3, a4 = c1
  157. sigma_1 = vector([k1H[0], k1H[1], k1H[2], k2H[0]])
  158. print(f"sigma_1: {sigma_1}")
  159. # b1 = a1, b2 = a2, b3 = a3, b4 = c2
  160. sigma_2 = vector([H[0], H[1], H[2], k2H[1]])
  161. print(f"sigma_2: {sigma_2}")
  162. # c1 = a4, c2 = b4, c3 = c4, c4 = c3
  163. sigma_3 = vector([H[3], k1H[3], k2H[3], k2H[2]])
  164. print(f"sigma_3: {sigma_3}")
  165. S_sigma_1_coeffs = inverse_matrix(H) * sigma_1
  166. S_sigma_2_coeffs = inverse_matrix(H) * sigma_2
  167. S_sigma_3_coeffs = inverse_matrix(H) * sigma_3