nova-ivc.sage 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305
  1. #!/usr/bin/env sage
  2. """
  3. # Resources on Nova
  4. [1] Revisiting the Nova Proof System on a Cycle of Curves
  5. https://eprint.iacr.org/2023/969.pdf
  6. [2] The zero-knowledge attack of the year might just have happened, or how Nova got broken
  7. https://www.zksecurity.xyz/blog/posts/nova-attack/
  8. [3] Nova: Recursive Zero-Knowledge Arguments from Folding Schemes
  9. https://eprint.iacr.org/2021/370.pdf
  10. # Important Notes
  11. * Nova uses a 2-cycle of curves E(Fp) = q, E(Fq) = p
  12. * It also uses a 250 bit hash function, whereby output values can be
  13. represented in both Fp and Fq.
  14. See [1] Section 3, paragraph on Hash Functions for a detailed description.
  15. # High Level Description
  16. z_{i + 1} = F(z_i, aux_i) is one step of the computation.
  17. Our goal is to represent several steps:
  18. z_{i + 1} = F(...F(F(z_0, aux_0), aux_1)..., aux_i)
  19. We can represent this as i + 1 proofs of the form:
  20. x₀ = hash(i, z₀, z_i, U_i)
  21. x₁ = hash(i + 1, z₀, z_{i + 1}, U_{i + 1})
  22. as well as a proof that:
  23. U_{i + 1} = fold(U_i, u_i)
  24. where u_i represents the proof for x₀, x₁ and U_i is the accumulator which
  25. contains the aggregated proofs, as well as the combined witness values.
  26. You can convince yourself that as long as the individual proofs are
  27. valid, you don't need to pass any data along from one proof to another.
  28. Since each proof contains a statement folding the previous accumulator, we
  29. recursively achieve a correct IVC.
  30. In practice the fold is done using pedersen commits so we do that in another
  31. circuit. But since now we have another circuit, we now have relations R1 and R2
  32. that both need to be folded.
  33. See [1] Section 4 Figures 1a and 1b, and Section 5.3 Fig 2.
  34. """
  35. q = 0x40000000000000000000000000000000224698fc0994a8dd8c46eb2100000001
  36. F1 = GF(q)
  37. E2 = EllipticCurve(F1, (0, 5))
  38. p = 0x40000000000000000000000000000000224698fc094cf91b992d30ed00000001
  39. F2 = GF(p)
  40. E1 = EllipticCurve(F2, (0, 5))
  41. # Base field of E1 is F2, and scalar field is F1
  42. # Base field of E2 is F1, and scalar field is F2
  43. assert E1.order() == q
  44. assert E2.order() == p
  45. Gen1 = [E1.random_point() for _ in range(1)]
  46. H1 = E1.random_point()
  47. Gen2 = [E2.random_point() for _ in range(1)]
  48. H2 = E2.random_point()
  49. commit1_blinds = {}
  50. commit2_blinds = {}
  51. def commit_impl(table, F, values, H, Gen):
  52. if values in table:
  53. b = table[values]
  54. else:
  55. b = F.random_element()
  56. table[values] = b
  57. C = b*H
  58. for v, G in zip(values, Gen):
  59. assert v.base_ring() == F
  60. C += v*G
  61. return C
  62. def commit1(values=()):
  63. return commit_impl(commit1_blinds, F1, values, H1, Gen1)
  64. def commit2(values=()):
  65. return commit_impl(commit2_blinds, F2, values, H2, Gen2)
  66. def hash_impl(table, F, E, key):
  67. if key in table:
  68. return table[key]
  69. c = F.random_element()
  70. while c > 2**250 - 1:
  71. c = F.random_element()
  72. table[key] = c
  73. return c
  74. hash1_table = {}
  75. hash2_table = {}
  76. def hash1(i, z0, z_i, accum_i):
  77. key = (i, z0, z_i, accum_i)
  78. return hash_impl(hash1_table, F1, E2, key)
  79. def hash2(i, z0, z_i, accum_i):
  80. key = (i, z0, z_i, accum_i)
  81. return hash_impl(hash2_table, F2, E1, key)
  82. # Setup phase for R1CS⁽¹⁾
  83. i = 0
  84. R1_accum0 = (E1(0), F1(0), E1(0), F1(0), F1(0))
  85. R2_accum0 = (E2(0), F2(0), E2(0), F2(0), F2(0))
  86. # We will calculate 5³ as F(z_{i + 1}) = 5 z_i
  87. R1_z0 = F1(1)
  88. # See [1] Section 3 Paragraph "Committed relaxed instances"
  89. # and Section 5.3
  90. # u0 is the initial dummy instance
  91. # Commitment to error vector
  92. R2_u0_E = commit2()
  93. # I think this value is μ in the original Nova paper
  94. R2_u0_s = F2(1)
  95. # Commitment to extended witness
  96. R2_u0_W = commit2()
  97. R2_x0 = F2(hash1(F1(0), R1_z0, R1_z0, R2_accum0))
  98. # We will ignore z on R2, we don't need it
  99. R2_x1 = hash2(F2(0), F2(0), F2(0), R1_accum0)
  100. R2_u0 = (R2_u0_E, R2_u0_s, R2_u0_W, R2_x0, R2_x1)
  101. # This should be extended to all the witness values for the calcs above
  102. R1_witness = (F1(0), R1_z0, R1_z0, R2_accum0, R2_u0, commit2())
  103. # This is weird since R2_u0_s is not in F1, but s is always 1 so it works
  104. R1_w1 = commit1(R1_witness)
  105. R2_accum1 = R2_accum0
  106. # Now we do the actual calc!
  107. R1_z1 = 5*R1_z0
  108. # Remember we said hash values are in both F1 and F2? Now we use that
  109. R1_x0 = F1(R2_x1)
  110. R1_x1 = hash1(F1(1), R1_z0, R1_z1, R2_accum0)
  111. R1_u1_E = commit1()
  112. R1_u1_s = F1(1)
  113. R1_u1 = (R1_u1_E, R1_u1_s, R1_w1, R1_x0, R1_x1)
  114. # ZK proof
  115. # R₁
  116. assert R1_z0 == R1_z0
  117. assert R2_u0_E == commit2()
  118. assert R2_u0_s == F2(1)
  119. assert F1(R2_x0) == hash1(F1(i), R1_z0, R1_z0, R2_accum0)
  120. assert R1_x0 == F1(R2_x1)
  121. assert R1_z1 == 5*R1_z0
  122. assert R1_x1 == hash1(F1(i + 1), R1_z0, R1_z1, R2_accum1)
  123. # Setup phase for R1CS⁽²⁾
  124. # We need to always do the part 2, after doing the part 1 otherwise
  125. # we leave things in an incomplete state.
  126. # We will ignore z on R2, we don't need it
  127. R2_witness = (F2(0), F2(0), F2(0), R1_accum0, R1_u1, commit2())
  128. R2_w1 = commit2(R2_witness)
  129. R1_accum1 = R1_u1
  130. R2_x0 = F2(R1_x1)
  131. R2_x1 = hash2(F2(i+1), F2(0), F2(0), R1_accum1)
  132. R2_u1_E = commit2()
  133. R2_u1_s = F2(1)
  134. R2_u1 = (R2_u1_E, R2_u1_s, R2_w1, R2_x0, R2_x1)
  135. # ZK proof
  136. # R₂
  137. assert R2_u1_E == commit2()
  138. assert R2_u1_s == F2(1)
  139. assert F2(R1_x0) == hash2(F2(0), F2(0), F2(0), R1_accum0)
  140. assert R2_x0 == F2(R1_x1)
  141. assert R2_x1 == hash2(F2(i+1), F2(0), F2(0), R1_accum1)
  142. # Setup phase complete
  143. # Next iteration
  144. i = 1
  145. # Fold(R2_u1, R2_accum1) -> R2_accum2
  146. R2_accum2 = (
  147. E2.random_point(), # E
  148. F2.random_element(), # s
  149. E2.random_point(), # W
  150. F2.random_element(), # x0
  151. F2.random_element() # x1
  152. )
  153. R1_witness = (F1(i), R1_z0, R1_z1, R2_accum1, R2_u1)
  154. R1_w2 = commit1(R1_witness)
  155. R1_z2 = 5*R1_z1
  156. R1_x0 = F1(R2_x1)
  157. R1_x1 = hash1(F1(i+1), R1_z0, R1_z2, R2_accum2)
  158. R1_u2_E = commit2()
  159. R1_u2_s = F2(1)
  160. R1_u2 = (R1_u2_E, R1_u2_s, R1_w2, R1_x0, R1_x1)
  161. # ZK proof
  162. # R₁
  163. # assert R2_accum2 == Fold(R2_u1, R2_accum1)
  164. assert R2_u1_E == commit2()
  165. assert R2_u1_s == F2(1)
  166. assert F1(R2_x0) == hash1(F1(i), R1_z0, R1_z1, R2_accum1)
  167. assert R1_x0 == F1(R2_x1)
  168. assert R1_z2 == 5*R1_z1
  169. assert R1_x1 == hash1(F1(i+1), R1_z0, R1_z2, R2_accum2)
  170. # Fold(R1_u2, R1_accum1) -> R1_accum2
  171. R1_accum2 = (
  172. E1.random_point(), # E
  173. F1.random_element(), # s
  174. E1.random_point(), # W
  175. F1.random_element(), # x0
  176. F1.random_element() # x1
  177. )
  178. R2_witness = (F2(i), F2(0), F2(0), R1_accum1, R1_u2)
  179. R2_w2 = commit2(R2_witness)
  180. R2_x0 = F2(R1_x1)
  181. R2_x1 = hash2(F2(i+1), F2(0), F2(0), R1_accum2)
  182. R2_u2_E = commit2()
  183. R2_u2_s = F2(1)
  184. R2_u2 = (R2_u2_E, R2_u2_s, R2_w2, R2_x0, R2_x1)
  185. # ZK proof
  186. # R₂
  187. # assert R1_accum2 == Fold(R1_u1, R1_accum1)
  188. assert R1_u2_E == commit2()
  189. assert R2_u2_s == F2(1)
  190. assert F2(R1_x0) == hash2(F2(i), F2(0), F2(0), R1_accum1)
  191. assert R2_x0 == F2(R1_x1)
  192. assert R2_x1 == hash2(F2(i+1), F2(0), F2(0), R1_accum2)
  193. # Next iteration
  194. i = 2
  195. # Fold(R2_u2, R2_accum2) -> R2_accum3
  196. R2_accum3 = (
  197. E2.random_point(), # E
  198. F2.random_element(), # s
  199. E2.random_point(), # W
  200. F2.random_element(), # x0
  201. F2.random_element() # x1
  202. )
  203. R1_witness = (F1(i), R1_z0, R1_z2, R2_accum2, R2_u2)
  204. R1_w3 = commit1(R1_witness)
  205. R1_z3 = 5*R1_z2
  206. R1_x0 = F1(R2_x1)
  207. R1_x1 = hash1(F1(i+1), R1_z0, R1_z3, R2_accum3)
  208. R1_u3_E = commit2()
  209. R1_u3_s = F2(1)
  210. R1_u3 = (R1_u3_E, R1_u3_s, R1_w3, R1_x0, R1_x1)
  211. # ZK proof
  212. # R₁
  213. # assert R2_accum3 == Fold(R2_u2, R2_accum2)
  214. assert R2_u2_E == commit2()
  215. assert R2_u2_s == F2(1)
  216. assert F1(R2_x0) == hash1(F1(i), R1_z0, R1_z2, R2_accum2)
  217. assert R1_x0 == F1(R2_x1)
  218. assert R1_z3 == 5*R1_z2
  219. assert R1_x1 == hash1(F1(i+1), R1_z0, R1_z3, R2_accum3)
  220. # Fold(R1_u3, R1_accum2) -> R1_accum3
  221. R1_accum3 = (
  222. E1.random_point(), # E
  223. F1.random_element(), # s
  224. E1.random_point(), # W
  225. F1.random_element(), # x0
  226. F1.random_element() # x1
  227. )
  228. R2_witness = (F2(i), F2(0), F2(0), R1_accum2, R1_u3)
  229. R2_w3 = commit2(R2_witness)
  230. R2_x0 = F2(R1_x1)
  231. R2_x1 = hash2(F2(i+1), F2(0), F2(0), R1_accum3)
  232. R2_u3_E = commit2()
  233. R2_u3_s = F2(1)
  234. R2_u3 = (R2_u3_E, R2_u3_s, R2_w3, R2_x0, R2_x1)
  235. # ZK proof
  236. # R₂
  237. # assert R1_accum3 == Fold(R1_u3, R1_accum2)
  238. assert R1_u3_E == commit2()
  239. assert R2_u3_s == F2(1)
  240. assert F2(R1_x0) == hash2(F2(i), F2(0), F2(0), R1_accum2)
  241. assert R2_x0 == F2(R1_x1)
  242. assert R2_x1 == hash2(F2(i+1), F2(0), F2(0), R1_accum3)