/ GrothProofs / test / runtests.jl
runtests.jl
  1  using GrothProofs
  2  using GrothAlgebra: bn254_scalar, evaluate
  3  using Test
  4  using Random
  5  
  6  include("random_circuits.jl")
  7  
  8  @testset "Groth16 full pipeline" begin
  9      # Build example R1CS and witness for r = x*y*z*u
 10      r1cs = create_r1cs_example_multiplication()
 11      x, y, z, u = 3, 5, 7, 11
 12      witness = create_witness_multiplication(x, y, z, u)
 13      @test is_satisfied(r1cs, witness)
 14  
 15      # Convert to QAP
 16      qap = r1cs_to_qap(r1cs)
 17  
 18      # Setup keys
 19      keypair = setup_full(qap; rng=MersenneTwister(42))
 20  
 21      # Prove with randomized r,s (seeded for reproducibility)
 22      proof = prove_full(keypair.pk, qap, witness; rng=MersenneTwister(1337))
 23  
 24      # Verify with correct public inputs
 25      # Arkworks prepare_inputs uses gamma_abc[1..] with inputs excluding the leading 1.
 26      # Our verify_full expects the leading 1 included at index 1, then the rest inputs.
 27      public_inputs = witness.values[1:r1cs.num_public]
 28      @test verify_full(keypair.vk, proof, public_inputs)
 29  
 30      # Negative test: mutate a public input -> should fail
 31      bad_inputs = copy(public_inputs)
 32      bad_inputs[2] = bad_inputs[2] + one(typeof(bad_inputs[2]))
 33      @test !verify_full(keypair.vk, proof, bad_inputs)
 34  
 35      # Negative test: tweak proof element -> should fail
 36      tweak = keypair.pk.alpha_g1
 37      bad_proof = Groth16Proof(proof.A + tweak, proof.B, proof.C)
 38      @test !verify_full(keypair.vk, bad_proof, public_inputs)
 39  end
 40  
 41  @testset "Groth16 randomness and multi-inputs" begin
 42      # Use multiple input sets and random r,s seeds
 43      r1cs = create_r1cs_example_multiplication()
 44      qap = r1cs_to_qap(r1cs)
 45  
 46      # Different inputs (x,y,z,u) to exercise IC composition
 47      inputs_list = [(2, 3, 5, 7), (9, 1, 4, 6), (13, 17, 19, 23)]
 48  
 49      # Deterministic keypair for repeatable tests
 50      keypair = setup_full(qap; rng=MersenneTwister(2024))
 51  
 52      for (idx, (x, y, z, u)) in enumerate(inputs_list)
 53          witness = create_witness_multiplication(x, y, z, u)
 54          @test is_satisfied(r1cs, witness)
 55          public_inputs = witness.values[1:r1cs.num_public]
 56  
 57          # Randomized prover seeds (non-zero randomness)
 58          rng = MersenneTwister(10_000 + idx)
 59          proof = prove_full(keypair.pk, qap, witness; rng=rng, debug_no_random=false)
 60          @test verify_full(keypair.vk, proof, public_inputs)
 61      end
 62  end
 63  
 64  @testset "Groth16 additional negatives" begin
 65      r1cs = create_r1cs_example_multiplication()
 66      qap = r1cs_to_qap(r1cs)
 67      keypair = setup_full(qap; rng=MersenneTwister(7))
 68  
 69      x, y, z, u = 4, 8, 3, 2
 70      witness = create_witness_multiplication(x, y, z, u)
 71      public_inputs = witness.values[1:r1cs.num_public]
 72      proof = prove_full(keypair.pk, qap, witness; rng=MersenneTwister(99))
 73  
 74      # Wrong public input length (drop last)
 75      @test !verify_full(keypair.vk, proof, public_inputs[1:end-1])
 76      # Wrong public input length (add extra)
 77      extra = vcat(public_inputs, public_inputs[end])
 78      @test !verify_full(keypair.vk, proof, extra)
 79      # Swap two public inputs
 80      swapped = copy(public_inputs)
 81      if length(swapped) >= 4
 82          tmp = swapped[3]
 83          swapped[3] = swapped[4]
 84          swapped[4] = tmp
 85          @test !verify_full(keypair.vk, proof, swapped)
 86      end
 87      # Tamper B and C separately
 88      bad_proof_B = Groth16Proof(proof.A, keypair.pk.beta_g2, proof.C)
 89      @test !verify_full(keypair.vk, bad_proof_B, public_inputs)
 90      bad_proof_C = Groth16Proof(proof.A, proof.B, keypair.pk.delta_g1)
 91      @test !verify_full(keypair.vk, bad_proof_C, public_inputs)
 92  end
 93  
 94  @testset "Groth16 sum-of-products circuit" begin
 95      # r = x*y + z*u
 96      r1cs = create_r1cs_example_sum_of_products()
 97      qap = r1cs_to_qap(r1cs)
 98  
 99      keypair = setup_full(qap; rng=MersenneTwister(4242))
100  
101      # Exercise multiple input patterns (zeros/ones/large)
102      inputs_list = [
103          (0, 5, 7, 11),
104          (1, 1, 1, 1),
105          (2, 0, 3, 0),
106          (12345, 6789, 101112, 131415),
107      ]
108  
109      for (x, y, z, u) in inputs_list
110          witness = create_witness_sum_of_products(x, y, z, u)
111          @test is_satisfied(r1cs, witness)
112          public_inputs = witness.values[1:r1cs.num_public]
113          proof = prove_full(keypair.pk, qap, witness; rng=MersenneTwister(x + y + z + u))
114          @test verify_full(keypair.vk, proof, public_inputs)
115  
116          # Prepared verifier path should agree
117          pvk = prepare_verifying_key(keypair.vk)
118          pin = prepare_inputs(pvk, public_inputs)
119          @test verify_with_prepared(pvk, proof, pin)
120      end
121  
122      # Rerandomization invariance: different seeds produce valid proofs
123      x, y, z, u = 3, 4, 5, 6
124      witness = create_witness_sum_of_products(x, y, z, u)
125      public_inputs = witness.values[1:r1cs.num_public]
126      proof1 = prove_full(keypair.pk, qap, witness; rng=MersenneTwister(1))
127      proof2 = prove_full(keypair.pk, qap, witness; rng=MersenneTwister(2))
128      @test verify_full(keypair.vk, proof1, public_inputs)
129      @test verify_full(keypair.vk, proof2, public_inputs)
130      pvk = prepare_verifying_key(keypair.vk)
131      pin = prepare_inputs(pvk, public_inputs)
132      @test verify_with_prepared(pvk, proof1, pin)
133      @test verify_with_prepared(pvk, proof2, pin)
134  end
135  
136  @testset "Groth16 affine product circuit" begin
137      r1cs = create_r1cs_example_affine_product()
138      qap = r1cs_to_qap(r1cs)
139      keypair = setup_full(qap; rng=MersenneTwister(5151))
140  
141      inputs_list = [
142          (0, 0, 0, 0),
143          (1, 2, 3, 4),
144          (7, 11, 13, 17),
145          (25, 30, 5, 9),
146      ]
147  
148      for (x, y, z, u) in inputs_list
149          witness = create_witness_affine_product(x, y, z, u)
150          @test is_satisfied(r1cs, witness)
151          public_inputs = witness.values[1:r1cs.num_public]
152          proof = prove_full(keypair.pk, qap, witness; rng=MersenneTwister(x + y + z + u + 1))
153          @test verify_full(keypair.vk, proof, public_inputs)
154  
155          pvk = prepare_verifying_key(keypair.vk)
156          pin = prepare_inputs(pvk, public_inputs)
157          @test verify_with_prepared(pvk, proof, pin)
158      end
159  
160      # Tamper helper witness entry to ensure detection
161      witness = create_witness_affine_product(2, 3, 5, 7)
162      bad = copy(witness.values)
163      bad[7] += one(eltype(bad))  # corrupt s1
164      bad_witness = Witness(bad)
165      @test !is_satisfied(r1cs, bad_witness)
166  end
167  
168  @testset "Groth16 square-offset circuit" begin
169      r1cs = create_r1cs_example_square_offset()
170      qap = r1cs_to_qap(r1cs)
171      keypair = setup_full(qap; rng=MersenneTwister(8181))
172  
173      inputs_list = [
174          (0, 0, 0),
175          (2, 3, 5),
176          (9, 1, 4),
177          (123, 456, 789),
178      ]
179  
180      for (x, y, c) in inputs_list
181          witness = create_witness_square_offset(x, y, c)
182          @test is_satisfied(r1cs, witness)
183          public_inputs = witness.values[1:r1cs.num_public]
184          proof = prove_full(keypair.pk, qap, witness; rng=MersenneTwister(x + y + c + 5))
185          @test verify_full(keypair.vk, proof, public_inputs)
186  
187          pvk = prepare_verifying_key(keypair.vk)
188          pin = prepare_inputs(pvk, public_inputs)
189          @test verify_with_prepared(pvk, proof, pin)
190      end
191  
192      # Wrong public input (offset) should fail verification
193      witness = create_witness_square_offset(4, 6, 8)
194      public_inputs = witness.values[1:r1cs.num_public]
195      proof = prove_full(keypair.pk, qap, witness; rng=MersenneTwister(4040))
196      bad_inputs = copy(public_inputs)
197      bad_inputs[end] += one(eltype(bad_inputs))
198      @test !verify_full(keypair.vk, proof, bad_inputs)
199  end
200  
201  @testset "Groth16 randomized circuits" begin
202      seeds = (101, 202, 303, 404, 505)
203      for seed in seeds
204          rng = MersenneTwister(seed)
205          circuit = generate_small_r1cs(rng)
206          r1cs = circuit.r1cs
207          witness = circuit.witness
208          @test is_satisfied(r1cs, witness)
209  
210          qap = r1cs_to_qap(r1cs)
211          keypair = setup_full(qap; rng=MersenneTwister(seed + 1))
212          proof = prove_full(keypair.pk, qap, witness; rng=MersenneTwister(seed + 2))
213  
214          public_inputs = witness.values[1:r1cs.num_public]
215          @test verify_full(keypair.vk, proof, public_inputs)
216  
217          pvk = prepare_verifying_key(keypair.vk)
218          pin = prepare_inputs(pvk, public_inputs)
219          @test verify_with_prepared(pvk, proof, pin)
220  
221          # Mutate a random public slot (beyond the leading 1) and expect failure
222          touched = circuit.used_public
223          if !isempty(touched)
224              bad_inputs = copy(public_inputs)
225              idx = rand(rng, touched)
226              bad_inputs[idx] += one(eltype(bad_inputs))
227              @test !verify_full(keypair.vk, proof, bad_inputs)
228          end
229  
230          h_dense = compute_h_polynomial(qap, witness; use_coset=false)
231          h_coset = compute_h_polynomial(qap, witness; use_coset=true)
232          @test h_coset == h_dense
233      end
234  end
235  
236  @testset "Groth16 prepared path negatives" begin
237      r1cs = create_r1cs_example_sum_of_products()
238      qap = r1cs_to_qap(r1cs)
239      keypair = setup_full(qap; rng=MersenneTwister(5150))
240  
241      witness = create_witness_sum_of_products(4, 6, 8, 10)
242      public_inputs = witness.values[1:r1cs.num_public]
243      proof = prove_full(keypair.pk, qap, witness; rng=MersenneTwister(42))
244  
245      pvk = prepare_verifying_key(keypair.vk)
246      prepared = prepare_inputs(pvk, public_inputs)
247      @test verify_with_prepared(pvk, proof, prepared)
248  
249      # prepare_inputs should reject empty or overlong vectors
250      @test_throws ArgumentError prepare_inputs(pvk, eltype(public_inputs)[])
251      overlong = vcat(public_inputs, public_inputs[end])
252      @test_throws ArgumentError prepare_inputs(pvk, overlong)
253  
254      # Mutated public inputs produce distinct prepared accumulator and must fail
255      bad_inputs = copy(public_inputs)
256      bad_inputs[3] += one(eltype(bad_inputs))
257      bad_prepared = prepare_inputs(pvk, bad_inputs)
258      @test !verify_with_prepared(pvk, proof, bad_prepared)
259  
260      # Tampering proof components is detected by prepared verifier as well
261      tweaked_proof = Groth16Proof(proof.A + keypair.pk.alpha_g1, proof.B, proof.C)
262      @test !verify_with_prepared(pvk, tweaked_proof, prepared)
263  end
264  
265  @testset "QAP divisibility spot-checks" begin
266      # Use both circuits to check U*V - W equals h*t at a few random points
267      for builder in (
268          create_r1cs_example_multiplication,
269          create_r1cs_example_sum_of_products,
270          create_r1cs_example_affine_product,
271          create_r1cs_example_square_offset,
272      )
273          r1cs = builder()
274          qap = r1cs_to_qap(r1cs)
275  
276          # Simple witness
277          w = builder === create_r1cs_example_multiplication ?
278              create_witness_multiplication(3, 5, 7, 11) :
279              builder === create_r1cs_example_sum_of_products ?
280                  create_witness_sum_of_products(3, 5, 7, 11) :
281                  builder === create_r1cs_example_affine_product ?
282                      create_witness_affine_product(2, 3, 5, 7) :
283                      create_witness_square_offset(2, 3, 5)
284  
285          # Build combined polynomials
286          u_poly = zero(qap.u[1])
287          v_poly = zero(qap.v[1])
288          w_poly = zero(qap.w[1])
289          for i in 1:qap.num_vars
290              u_poly = u_poly + w.values[i] * qap.u[i]
291              v_poly = v_poly + w.values[i] * qap.v[i]
292              w_poly = w_poly + w.values[i] * qap.w[i]
293          end
294          h_dense = compute_h_polynomial(qap, w; use_coset=false)
295          h_coset = compute_h_polynomial(qap, w; use_coset=true)
296          @test h_coset == h_dense
297          h_poly = h_coset
298          t_poly = qap.t
299  
300          # Choose a few points outside the domain [1..n]
301          pts = [bn254_scalar(qap.num_constraints + k) for k in (1, 2, 3)]
302          for x in pts
303              lhs = evaluate(u_poly, x) * evaluate(v_poly, x) - evaluate(w_poly, x)
304              rhs = evaluate(h_poly, x) * evaluate(t_poly, x)
305              if lhs != rhs
306                  println("[QAP check] builder=$(builder) x=$(Integer(x.value)) lhs=$(Integer(lhs.value)) rhs=$(Integer(rhs.value))")
307              end
308              @test lhs == rhs
309          end
310      end
311  end
312  
313  @testset "QAP coset vanishing cases" begin
314      r1cs_power = create_r1cs_example_square_offset()
315      qap_power = r1cs_to_qap(r1cs_power)
316      @test qap_power.num_constraints == qap_power.domain.size
317      witness_power = create_witness_square_offset(2, 3, 5)
318      h_dense_power = compute_h_polynomial(qap_power, witness_power; use_coset=false)
319      h_coset_power = compute_h_polynomial(qap_power, witness_power; use_coset=true)
320      @test h_coset_power == h_dense_power
321  
322      r1cs_subset = create_r1cs_example_multiplication()
323      qap_subset = r1cs_to_qap(r1cs_subset)
324      @test qap_subset.num_constraints < qap_subset.domain.size
325      witness_subset = create_witness_multiplication(3, 5, 7, 11)
326      h_dense_subset = compute_h_polynomial(qap_subset, witness_subset; use_coset=false)
327      h_coset_subset = compute_h_polynomial(qap_subset, witness_subset; use_coset=true)
328      @test h_coset_subset == h_dense_subset
329  end
330  
331  @testset "Groth16 rejects unsatisfied witness" begin
332      r1cs = create_r1cs_example_multiplication()
333      qap = r1cs_to_qap(r1cs)
334      keypair = setup_full(qap; rng=MersenneTwister(6060))
335  
336      witness = create_witness_multiplication(3, 5, 7, 11)
337      bad_values = copy(witness.values)
338      bad_values[2] += one(eltype(bad_values))  # perturb expected output r
339      bad_witness = Witness(bad_values)
340      @test !is_satisfied(r1cs, bad_witness)
341  
342      @test_throws ErrorException prove_full(keypair.pk, qap, bad_witness; rng=MersenneTwister(1234))
343  end