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