| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305 |
- #!/usr/bin/env sage
- """
- # Resources on Nova
- [1] Revisiting the Nova Proof System on a Cycle of Curves
- https://eprint.iacr.org/2023/969.pdf
- [2] The zero-knowledge attack of the year might just have happened, or how Nova got broken
- https://www.zksecurity.xyz/blog/posts/nova-attack/
- [3] Nova: Recursive Zero-Knowledge Arguments from Folding Schemes
- https://eprint.iacr.org/2021/370.pdf
- # Important Notes
- * Nova uses a 2-cycle of curves E(Fp) = q, E(Fq) = p
- * It also uses a 250 bit hash function, whereby output values can be
- represented in both Fp and Fq.
- See [1] Section 3, paragraph on Hash Functions for a detailed description.
- # High Level Description
- z_{i + 1} = F(z_i, aux_i) is one step of the computation.
- Our goal is to represent several steps:
- z_{i + 1} = F(...F(F(z_0, aux_0), aux_1)..., aux_i)
- We can represent this as i + 1 proofs of the form:
- x₀ = hash(i, z₀, z_i, U_i)
- x₁ = hash(i + 1, z₀, z_{i + 1}, U_{i + 1})
- as well as a proof that:
- U_{i + 1} = fold(U_i, u_i)
- where u_i represents the proof for x₀, x₁ and U_i is the accumulator which
- contains the aggregated proofs, as well as the combined witness values.
- You can convince yourself that as long as the individual proofs are
- valid, you don't need to pass any data along from one proof to another.
- Since each proof contains a statement folding the previous accumulator, we
- recursively achieve a correct IVC.
- In practice the fold is done using pedersen commits so we do that in another
- circuit. But since now we have another circuit, we now have relations R1 and R2
- that both need to be folded.
- See [1] Section 4 Figures 1a and 1b, and Section 5.3 Fig 2.
- """
- q = 0x40000000000000000000000000000000224698fc0994a8dd8c46eb2100000001
- F1 = GF(q)
- E2 = EllipticCurve(F1, (0, 5))
- p = 0x40000000000000000000000000000000224698fc094cf91b992d30ed00000001
- F2 = GF(p)
- E1 = EllipticCurve(F2, (0, 5))
- # Base field of E1 is F2, and scalar field is F1
- # Base field of E2 is F1, and scalar field is F2
- assert E1.order() == q
- assert E2.order() == p
- Gen1 = [E1.random_point() for _ in range(1)]
- H1 = E1.random_point()
- Gen2 = [E2.random_point() for _ in range(1)]
- H2 = E2.random_point()
- commit1_blinds = {}
- commit2_blinds = {}
- def commit_impl(table, F, values, H, Gen):
- if values in table:
- b = table[values]
- else:
- b = F.random_element()
- table[values] = b
- C = b*H
- for v, G in zip(values, Gen):
- assert v.base_ring() == F
- C += v*G
- return C
- def commit1(values=()):
- return commit_impl(commit1_blinds, F1, values, H1, Gen1)
- def commit2(values=()):
- return commit_impl(commit2_blinds, F2, values, H2, Gen2)
- def hash_impl(table, F, E, key):
- if key in table:
- return table[key]
- c = F.random_element()
- while c > 2**250 - 1:
- c = F.random_element()
- table[key] = c
- return c
- hash1_table = {}
- hash2_table = {}
- def hash1(i, z0, z_i, accum_i):
- key = (i, z0, z_i, accum_i)
- return hash_impl(hash1_table, F1, E2, key)
- def hash2(i, z0, z_i, accum_i):
- key = (i, z0, z_i, accum_i)
- return hash_impl(hash2_table, F2, E1, key)
- # Setup phase for R1CS⁽¹⁾
- i = 0
- R1_accum0 = (E1(0), F1(0), E1(0), F1(0), F1(0))
- R2_accum0 = (E2(0), F2(0), E2(0), F2(0), F2(0))
- # We will calculate 5³ as F(z_{i + 1}) = 5 z_i
- R1_z0 = F1(1)
- # See [1] Section 3 Paragraph "Committed relaxed instances"
- # and Section 5.3
- # u0 is the initial dummy instance
- # Commitment to error vector
- R2_u0_E = commit2()
- # I think this value is μ in the original Nova paper
- R2_u0_s = F2(1)
- # Commitment to extended witness
- R2_u0_W = commit2()
- R2_x0 = F2(hash1(F1(0), R1_z0, R1_z0, R2_accum0))
- # We will ignore z on R2, we don't need it
- R2_x1 = hash2(F2(0), F2(0), F2(0), R1_accum0)
- R2_u0 = (R2_u0_E, R2_u0_s, R2_u0_W, R2_x0, R2_x1)
- # This should be extended to all the witness values for the calcs above
- R1_witness = (F1(0), R1_z0, R1_z0, R2_accum0, R2_u0, commit2())
- # This is weird since R2_u0_s is not in F1, but s is always 1 so it works
- R1_w1 = commit1(R1_witness)
- R2_accum1 = R2_accum0
- # Now we do the actual calc!
- R1_z1 = 5*R1_z0
- # Remember we said hash values are in both F1 and F2? Now we use that
- R1_x0 = F1(R2_x1)
- R1_x1 = hash1(F1(1), R1_z0, R1_z1, R2_accum0)
- R1_u1_E = commit1()
- R1_u1_s = F1(1)
- R1_u1 = (R1_u1_E, R1_u1_s, R1_w1, R1_x0, R1_x1)
- # ZK proof
- # R₁
- assert R1_z0 == R1_z0
- assert R2_u0_E == commit2()
- assert R2_u0_s == F2(1)
- assert F1(R2_x0) == hash1(F1(i), R1_z0, R1_z0, R2_accum0)
- assert R1_x0 == F1(R2_x1)
- assert R1_z1 == 5*R1_z0
- assert R1_x1 == hash1(F1(i + 1), R1_z0, R1_z1, R2_accum1)
- # Setup phase for R1CS⁽²⁾
- # We need to always do the part 2, after doing the part 1 otherwise
- # we leave things in an incomplete state.
- # We will ignore z on R2, we don't need it
- R2_witness = (F2(0), F2(0), F2(0), R1_accum0, R1_u1, commit2())
- R2_w1 = commit2(R2_witness)
- R1_accum1 = R1_u1
- R2_x0 = F2(R1_x1)
- R2_x1 = hash2(F2(i+1), F2(0), F2(0), R1_accum1)
- R2_u1_E = commit2()
- R2_u1_s = F2(1)
- R2_u1 = (R2_u1_E, R2_u1_s, R2_w1, R2_x0, R2_x1)
- # ZK proof
- # R₂
- assert R2_u1_E == commit2()
- assert R2_u1_s == F2(1)
- assert F2(R1_x0) == hash2(F2(0), F2(0), F2(0), R1_accum0)
- assert R2_x0 == F2(R1_x1)
- assert R2_x1 == hash2(F2(i+1), F2(0), F2(0), R1_accum1)
- # Setup phase complete
- # Next iteration
- i = 1
- # Fold(R2_u1, R2_accum1) -> R2_accum2
- R2_accum2 = (
- E2.random_point(), # E
- F2.random_element(), # s
- E2.random_point(), # W
- F2.random_element(), # x0
- F2.random_element() # x1
- )
- R1_witness = (F1(i), R1_z0, R1_z1, R2_accum1, R2_u1)
- R1_w2 = commit1(R1_witness)
- R1_z2 = 5*R1_z1
- R1_x0 = F1(R2_x1)
- R1_x1 = hash1(F1(i+1), R1_z0, R1_z2, R2_accum2)
- R1_u2_E = commit2()
- R1_u2_s = F2(1)
- R1_u2 = (R1_u2_E, R1_u2_s, R1_w2, R1_x0, R1_x1)
- # ZK proof
- # R₁
- # assert R2_accum2 == Fold(R2_u1, R2_accum1)
- assert R2_u1_E == commit2()
- assert R2_u1_s == F2(1)
- assert F1(R2_x0) == hash1(F1(i), R1_z0, R1_z1, R2_accum1)
- assert R1_x0 == F1(R2_x1)
- assert R1_z2 == 5*R1_z1
- assert R1_x1 == hash1(F1(i+1), R1_z0, R1_z2, R2_accum2)
- # Fold(R1_u2, R1_accum1) -> R1_accum2
- R1_accum2 = (
- E1.random_point(), # E
- F1.random_element(), # s
- E1.random_point(), # W
- F1.random_element(), # x0
- F1.random_element() # x1
- )
- R2_witness = (F2(i), F2(0), F2(0), R1_accum1, R1_u2)
- R2_w2 = commit2(R2_witness)
- R2_x0 = F2(R1_x1)
- R2_x1 = hash2(F2(i+1), F2(0), F2(0), R1_accum2)
- R2_u2_E = commit2()
- R2_u2_s = F2(1)
- R2_u2 = (R2_u2_E, R2_u2_s, R2_w2, R2_x0, R2_x1)
- # ZK proof
- # R₂
- # assert R1_accum2 == Fold(R1_u1, R1_accum1)
- assert R1_u2_E == commit2()
- assert R2_u2_s == F2(1)
- assert F2(R1_x0) == hash2(F2(i), F2(0), F2(0), R1_accum1)
- assert R2_x0 == F2(R1_x1)
- assert R2_x1 == hash2(F2(i+1), F2(0), F2(0), R1_accum2)
- # Next iteration
- i = 2
- # Fold(R2_u2, R2_accum2) -> R2_accum3
- R2_accum3 = (
- E2.random_point(), # E
- F2.random_element(), # s
- E2.random_point(), # W
- F2.random_element(), # x0
- F2.random_element() # x1
- )
- R1_witness = (F1(i), R1_z0, R1_z2, R2_accum2, R2_u2)
- R1_w3 = commit1(R1_witness)
- R1_z3 = 5*R1_z2
- R1_x0 = F1(R2_x1)
- R1_x1 = hash1(F1(i+1), R1_z0, R1_z3, R2_accum3)
- R1_u3_E = commit2()
- R1_u3_s = F2(1)
- R1_u3 = (R1_u3_E, R1_u3_s, R1_w3, R1_x0, R1_x1)
- # ZK proof
- # R₁
- # assert R2_accum3 == Fold(R2_u2, R2_accum2)
- assert R2_u2_E == commit2()
- assert R2_u2_s == F2(1)
- assert F1(R2_x0) == hash1(F1(i), R1_z0, R1_z2, R2_accum2)
- assert R1_x0 == F1(R2_x1)
- assert R1_z3 == 5*R1_z2
- assert R1_x1 == hash1(F1(i+1), R1_z0, R1_z3, R2_accum3)
- # Fold(R1_u3, R1_accum2) -> R1_accum3
- R1_accum3 = (
- E1.random_point(), # E
- F1.random_element(), # s
- E1.random_point(), # W
- F1.random_element(), # x0
- F1.random_element() # x1
- )
- R2_witness = (F2(i), F2(0), F2(0), R1_accum2, R1_u3)
- R2_w3 = commit2(R2_witness)
- R2_x0 = F2(R1_x1)
- R2_x1 = hash2(F2(i+1), F2(0), F2(0), R1_accum3)
- R2_u3_E = commit2()
- R2_u3_s = F2(1)
- R2_u3 = (R2_u3_E, R2_u3_s, R2_w3, R2_x0, R2_x1)
- # ZK proof
- # R₂
- # assert R1_accum3 == Fold(R1_u3, R1_accum2)
- assert R1_u3_E == commit2()
- assert R2_u3_s == F2(1)
- assert F2(R1_x0) == hash2(F2(i), F2(0), F2(0), R1_accum2)
- assert R2_x0 == F2(R1_x1)
- assert R2_x1 == hash2(F2(i+1), F2(0), F2(0), R1_accum3)
|