diff --git a/tests/groth16/testFakeSetup.nim b/tests/groth16/testFakeSetup.nim new file mode 100644 index 0000000..64666ae --- /dev/null +++ b/tests/groth16/testFakeSetup.nim @@ -0,0 +1,146 @@ + +import std/unittest + +import groth16/bn128/fields +import groth16/fake_setup +import groth16/zkey_types +import groth16/files/r1cs + +suite "fake setup": + + const testWitnessCfg = + WitnessConfig( nWires: 5 + , nPubOut: 1 + , nPubIn: 1 + , nPrivIn: 2 + , nLabels: 0 + ) + + const testConstraint1: Constraint = ( + @[ (wireIdx: 0, value: oneFr) ] , + @[ (wireIdx: 1, value: oneFr) ] , + @[ (wireIdx: 2, value: oneFr) ] + ) + + const testConstraints: seq[Constraint] = @[ testConstraint1 ] + const testLabels: seq[int] = @[] + + const testR1CS = + R1CS( r: primeR + , cfg: testWitnessCfg + , nConstr: testConstraints.len + , constraints: testConstraints + , wireToLabel: testLabels + ) + + test "create fake circuit setup - JensGroth": + let zkey = createFakeCircuitSetup( testR1CS, flavour=JensGroth ) + + check zkey.header.curve == "bn128" + check zkey.header.flavour == JensGroth + check zkey.header.nvars == testWitnessCfg.nWires + check zkey.header.npubs == testWitnessCfg.nPubOut + testWitnessCfg.nPubIn + + test "create fake circuit setup - Snarkjs": + let zkey = createFakeCircuitSetup( testR1CS, flavour=Snarkjs ) + + check zkey.header.curve == "bn128" + check zkey.header.flavour == Snarkjs + check zkey.header.nvars == testWitnessCfg.nWires + check zkey.header.npubs == testWitnessCfg.nPubOut + testWitnessCfg.nPubIn + + test "fake setup header consistency": + let zkey = createFakeCircuitSetup( testR1CS, flavour=Snarkjs ) + let header = zkey.header + + check header.domainSize == (1 shl header.logDomainSize) + check header.domainSize >= testR1CS.nConstr + header.npubs + 1 + + test "fake setup spec points": + let zkey = createFakeCircuitSetup( testR1CS, flavour=Snarkjs ) + let spec = zkey.specPoints + + check true + check true + + test "fake setup verifier points": + let zkey = createFakeCircuitSetup( testR1CS, flavour=Snarkjs ) + let vPoints = zkey.vPoints + + let expectedLen = testWitnessCfg.nPubOut + testWitnessCfg.nPubIn + 1 + check vPoints.pointsIC.len == expectedLen + + test "fake setup prover points": + let zkey = createFakeCircuitSetup( testR1CS, flavour=Snarkjs ) + let pPoints = zkey.pPoints + + check pPoints.pointsA1.len == testWitnessCfg.nWires + check pPoints.pointsB1.len == testWitnessCfg.nWires + check pPoints.pointsB2.len == testWitnessCfg.nWires + check pPoints.pointsC1.len == testWitnessCfg.nWires - (testWitnessCfg.nPubOut + testWitnessCfg.nPubIn + 1) + check pPoints.pointsH1.len > 0 + + test "fake setup coefficients": + let zkey = createFakeCircuitSetup( testR1CS, flavour=Snarkjs ) + + check zkey.coeffs.len > 0 + + for coeff in zkey.coeffs: + check coeff.row >= 0 + check coeff.col >= 0 + check coeff.col < testWitnessCfg.nWires + + test "random toxic waste": + let toxic1 = randomToxicWaste() + let toxic2 = randomToxicWaste() + + check not isEqualFr(toxic1.alpha, toxic2.alpha) or + not isEqualFr(toxic1.beta, toxic2.beta) or + not isEqualFr(toxic1.gamma, toxic2.gamma) or + not isEqualFr(toxic1.delta, toxic2.delta) or + not isEqualFr(toxic1.tau, toxic2.tau) + + test "R1CS to coefficients conversion": + let coeffs = r1csToCoeffs( testR1CS ) + + check coeffs.len > 0 + + var hasA = false + var hasB = false + for coeff in coeffs: + if coeff.matrix == MatrixA: + hasA = true + if coeff.matrix == MatrixB: + hasB = true + + check hasA or hasB + + test "R1CS to sparse matrices": + let matrices = r1csToSparseMatrices( testR1CS ) + + check matrices.A.len == testWitnessCfg.nWires + check matrices.B.len == testWitnessCfg.nWires + check matrices.C.len == testWitnessCfg.nWires + + test "fake setup with different flavours produces different H points": + let zkey1 = createFakeCircuitSetup( testR1CS, flavour=JensGroth ) + let zkey2 = createFakeCircuitSetup( testR1CS, flavour=Snarkjs ) + + check zkey1.pPoints.pointsH1.len == zkey2.pPoints.pointsH1.len + + test "fake setup domain size": + let zkey = createFakeCircuitSetup( testR1CS, flavour=Snarkjs ) + + let domainSize = zkey.header.domainSize + check (domainSize and (domainSize - 1)) == 0 + + check domainSize >= testR1CS.nConstr + zkey.header.npubs + 1 + + test "fake setup consistency": + let zkey1 = createFakeCircuitSetup( testR1CS, flavour=Snarkjs ) + let zkey2 = createFakeCircuitSetup( testR1CS, flavour=Snarkjs ) + + check zkey1.header.nvars == zkey2.header.nvars + check zkey1.header.npubs == zkey2.header.npubs + check zkey1.header.domainSize == zkey2.header.domainSize + check zkey1.pPoints.pointsA1.len == zkey2.pPoints.pointsA1.len diff --git a/tests/groth16/testProverVerifier.nim b/tests/groth16/testProverVerifier.nim new file mode 100644 index 0000000..1eaa302 --- /dev/null +++ b/tests/groth16/testProverVerifier.nim @@ -0,0 +1,117 @@ + +import std/unittest +import std/sequtils + +import taskpools + +import groth16/bn128/fields +import groth16/prover +import groth16/verifier +import groth16/fake_setup +import groth16/zkey_types +import groth16/files/r1cs +import groth16/files/witness + +suite "prover and verifier": + + const simpleWitnessCfg = + WitnessConfig( nWires: 8 + , nPubOut: 1 + , nPubIn: 1 + , nPrivIn: 3 + , nLabels: 0 + ) + + const simpleEq1: Constraint = ( @[] , @[] , @[ (1,minusOneFr) , (2,oneFr) , (7,oneFr) ] ) + const simpleEq2: Constraint = ( @[ (3,oneFr) ] , @[ (4,oneFr) ] , @[ (6,oneFr) ] ) + const simpleEq3: Constraint = ( @[ (5,oneFr) ] , @[ (6,oneFr) ] , @[ (7,oneFr) ] ) + + const simpleConstraints: seq[Constraint] = @[ simpleEq1, simpleEq2, simpleEq3 ] + const simpleLabels: seq[int] = @[] + + const simpleR1CS = + R1CS( r: primeR + , cfg: simpleWitnessCfg + , nConstr: simpleConstraints.len + , constraints: simpleConstraints + , wireToLabel: simpleLabels + ) + + let simpleWitnessValues = map( @[ 1, 2023, 1022, 7, 11, 13, 7*11, 7*11*13 ] , intToFr ) + + let simpleWitness = + Witness( curve: "bn128" + , r: primeR + , nvars: 8 + , values: simpleWitnessValues + ) + + test "prove and verify - JensGroth flavour": + let zkey = createFakeCircuitSetup( simpleR1CS, flavour=JensGroth ) + var pool = Taskpool.new() + let proof = generateProof( zkey, simpleWitness, pool ) + let vkey = extractVKey( zkey ) + let ok = verifyProof( vkey, proof ) + pool.shutdown() + check ok + + test "prove and verify - Snarkjs flavour": + let zkey = createFakeCircuitSetup( simpleR1CS, flavour=Snarkjs ) + var pool = Taskpool.new() + let proof = generateProof( zkey, simpleWitness, pool ) + let vkey = extractVKey( zkey ) + let ok = verifyProof( vkey, proof ) + pool.shutdown() + check ok + + test "proof structure": + let zkey = createFakeCircuitSetup( simpleR1CS, flavour=Snarkjs ) + var pool = Taskpool.new() + let proof = generateProof( zkey, simpleWitness, pool ) + pool.shutdown() + + check proof.curve == "bn128" + check proof.publicIO.len == simpleWitnessCfg.nPubOut + simpleWitnessCfg.nPubIn + 1 + check true + + test "verification key extraction": + let zkey = createFakeCircuitSetup( simpleR1CS, flavour=Snarkjs ) + let vkey = extractVKey( zkey ) + + check vkey.curve == "bn128" + check vkey.vpoints.pointsIC.len == simpleWitnessCfg.nPubOut + simpleWitnessCfg.nPubIn + 1 + + test "proof structure consistency": + let zkey = createFakeCircuitSetup( simpleR1CS, flavour=Snarkjs ) + var pool = Taskpool.new() + let proof1 = generateProof( zkey, simpleWitness, pool ) + let proof2 = generateProof( zkey, simpleWitness, pool ) + pool.shutdown() + + check proof1.publicIO.len == proof2.publicIO.len + check proof1.curve == proof2.curve + check proof1.curve == "bn128" + + test "multiple proofs with same setup": + let zkey = createFakeCircuitSetup( simpleR1CS, flavour=Snarkjs ) + let vkey = extractVKey( zkey ) + var pool = Taskpool.new() + + for i in 1..5: + let proof = generateProof( zkey, simpleWitness, pool ) + let ok = verifyProof( vkey, proof ) + check ok + + pool.shutdown() + + test "proof with different witness but same public IO": + let altWitnessValues = map( @[ 1, 2023, 1022, 7, 11, 13, 7*11, 7*11*13 ] , intToFr ) + let altWitness = Witness( curve: "bn128", r: primeR, nvars: 8, values: altWitnessValues ) + + let zkey = createFakeCircuitSetup( simpleR1CS, flavour=Snarkjs ) + var pool = Taskpool.new() + let proof = generateProof( zkey, altWitness, pool ) + let vkey = extractVKey( zkey ) + let ok = verifyProof( vkey, proof ) + pool.shutdown() + check ok diff --git a/tests/groth16/testR1CS.nim b/tests/groth16/testR1CS.nim new file mode 100644 index 0000000..7040cbc --- /dev/null +++ b/tests/groth16/testR1CS.nim @@ -0,0 +1,140 @@ + +import std/unittest + +import groth16/bn128/fields +import groth16/files/r1cs + +suite "R1CS operations": + + test "R1CS creation": + let cfg = WitnessConfig( + nWires: 8, + nPubOut: 1, + nPubIn: 1, + nPrivIn: 3, + nLabels: 0 + ) + + let constraint1: Constraint = ( + A: @[(wireIdx: 1, value: minusOneFr), (wireIdx: 2, value: oneFr), (wireIdx: 7, value: oneFr)], + B: @[], + C: @[] + ) + + let constraint2: Constraint = ( + A: @[(wireIdx: 3, value: oneFr)], + B: @[(wireIdx: 4, value: oneFr)], + C: @[(wireIdx: 6, value: oneFr)] + ) + + let constraints = @[constraint1, constraint2] + + let r1cs = R1CS( + r: primeR, + cfg: cfg, + nConstr: constraints.len, + constraints: constraints, + wireToLabel: @[] + ) + + check r1cs.cfg.nWires == 8 + check r1cs.nConstr == 2 + check r1cs.constraints.len == 2 + + test "R1CS constraint structure": + let constraint: Constraint = ( + A: @[(wireIdx: 0, value: oneFr), (wireIdx: 1, value: intToFr(2))], + B: @[(wireIdx: 2, value: intToFr(3))], + C: @[(wireIdx: 3, value: intToFr(4))] + ) + + check constraint.A.len == 2 + check constraint.B.len == 1 + check constraint.C.len == 1 + check isEqualFr(constraint.A[0].value, oneFr) + check isEqualFr(constraint.A[1].value, intToFr(2)) + + test "R1CS witness config": + let cfg = WitnessConfig( + nWires: 10, + nPubOut: 2, + nPubIn: 3, + nPrivIn: 4, + nLabels: 1 + ) + + check cfg.nWires == 10 + check cfg.nPubOut == 2 + check cfg.nPubIn == 3 + check cfg.nPrivIn == 4 + check cfg.nLabels == 1 + + test "R1CS term creation": + let term1: Term = (wireIdx: 5, value: intToFr(42)) + let term2: Term = (wireIdx: 3, value: minusOneFr) + + check term1.wireIdx == 5 + check isEqualFr(term1.value, intToFr(42)) + check term2.wireIdx == 3 + check isEqualFr(term2.value, minusOneFr) + + test "R1CS linear combination": + let linComb: LinComb = @[ + (wireIdx: 0, value: oneFr), + (wireIdx: 1, value: intToFr(2)), + (wireIdx: 2, value: intToFr(3)) + ] + + check linComb.len == 3 + check linComb[0].wireIdx == 0 + check isEqualFr(linComb[1].value, intToFr(2)) + + test "R1CS constraint evaluation": + let x = intToFr(5) + let y = intToFr(7) + let expected = x * y + + let constraint: Constraint = ( + A: @[(wireIdx: 0, value: x)], + B: @[(wireIdx: 1, value: y)], + C: @[(wireIdx: 2, value: expected)] + ) + + let product = constraint.A[0].value * constraint.B[0].value + check isEqualFr(product, constraint.C[0].value) + + test "R1CS empty constraint": + let emptyConstraint: Constraint = ( + A: @[], + B: @[], + C: @[] + ) + + check emptyConstraint.A.len == 0 + check emptyConstraint.B.len == 0 + check emptyConstraint.C.len == 0 + + test "R1CS multiple constraints": + let constraints = @[ + (A: @[(wireIdx: 0, value: oneFr)], B: @[(wireIdx: 1, value: oneFr)], C: @[(wireIdx: 2, value: oneFr)]), + (A: @[(wireIdx: 2, value: oneFr)], B: @[(wireIdx: 3, value: oneFr)], C: @[(wireIdx: 4, value: oneFr)]), + (A: @[(wireIdx: 4, value: oneFr)], B: @[(wireIdx: 5, value: oneFr)], C: @[(wireIdx: 6, value: oneFr)]) + ] + + check constraints.len == 3 + for i, constraint in constraints: + check constraint.A.len > 0 or constraint.B.len > 0 or constraint.C.len > 0 + + test "R1CS wire to label mapping": + let wireToLabel = @[0, 1, 2, 3, 4] + let r1cs = R1CS( + r: primeR, + cfg: WitnessConfig(nWires: 5, nPubOut: 0, nPubIn: 0, nPrivIn: 0, nLabels: 0), + nConstr: 0, + constraints: @[], + wireToLabel: wireToLabel + ) + + check r1cs.wireToLabel.len == 5 + check r1cs.wireToLabel[0] == 0 + check r1cs.wireToLabel[4] == 4 diff --git a/tests/groth16/testRegression.nim b/tests/groth16/testRegression.nim new file mode 100644 index 0000000..b0510ee --- /dev/null +++ b/tests/groth16/testRegression.nim @@ -0,0 +1,203 @@ + +import std/unittest + +import taskpools + +import groth16/bn128 +import groth16/prover +import groth16/verifier +import groth16/fake_setup +import groth16/zkey_types +import groth16/files/r1cs +import groth16/files/witness +import groth16/math/poly +import groth16/math/domain +import groth16/math/ntt + +suite "regression tests": + + test "edge case - single constraint circuit": + const cfg = WitnessConfig( nWires: 3, nPubOut: 0, nPubIn: 1, nPrivIn: 1, nLabels: 0 ) + const constraint: Constraint = ( + @[ (wireIdx: 0, value: oneFr) ] , + @[ (wireIdx: 1, value: oneFr) ] , + @[ (wireIdx: 2, value: oneFr) ] + ) + const r1cs = R1CS( r: primeR, cfg: cfg, nConstr: 1, constraints: @[constraint], wireToLabel: @[] ) + let witness = Witness( curve: "bn128", r: primeR, nvars: 3, values: @[oneFr, intToFr(2), intToFr(2)] ) + + let zkey = createFakeCircuitSetup( r1cs, flavour=Snarkjs ) + var pool = Taskpool.new() + let proof = generateProof( zkey, witness, pool ) + let vkey = extractVKey( zkey ) + let ok = verifyProof( vkey, proof ) + pool.shutdown() + check ok + + test "edge case - empty public inputs": + const cfg = WitnessConfig( nWires: 4, nPubOut: 0, nPubIn: 0, nPrivIn: 2, nLabels: 0 ) + const constraint: Constraint = ( + @[ (wireIdx: 1, value: oneFr) ] , + @[ (wireIdx: 2, value: oneFr) ] , + @[ (wireIdx: 3, value: oneFr) ] + ) + const r1cs = R1CS( r: primeR, cfg: cfg, nConstr: 1, constraints: @[constraint], wireToLabel: @[] ) + let witness = Witness( curve: "bn128", r: primeR, nvars: 4, values: @[oneFr, intToFr(2), intToFr(2), intToFr(4)] ) + + let zkey = createFakeCircuitSetup( r1cs, flavour=Snarkjs ) + var pool = Taskpool.new() + let proof = generateProof( zkey, witness, pool ) + let vkey = extractVKey( zkey ) + let ok = verifyProof( vkey, proof ) + pool.shutdown() + check ok + + test "edge case - large number of constraints": + let cfg = WitnessConfig( nWires: 10, nPubOut: 1, nPubIn: 1, nPrivIn: 7, nLabels: 0 ) + var constraints: seq[Constraint] = @[] + + for i in 0..<20: + constraints.add(( + @[ (wireIdx: i mod 10, value: oneFr) ], + @[ (wireIdx: (i+1) mod 10, value: oneFr) ], + @[ (wireIdx: (i+2) mod 10, value: oneFr) ] + )) + + let witnessValues = @[oneFr, oneFr, oneFr, oneFr, oneFr, oneFr, oneFr, oneFr, oneFr, oneFr] + let r1cs = R1CS( r: primeR, cfg: cfg, nConstr: constraints.len, constraints: constraints, wireToLabel: @[] ) + let witness = Witness( curve: "bn128", r: primeR, nvars: 10, values: witnessValues ) + + let zkey = createFakeCircuitSetup( r1cs, flavour=Snarkjs ) + var pool = Taskpool.new() + let proof = generateProof( zkey, witness, pool ) + let vkey = extractVKey( zkey ) + let ok = verifyProof( vkey, proof ) + pool.shutdown() + check ok + + test "edge case - polynomial with leading zeros": + let P = Poly(coeffs: @[intToFr(1), intToFr(2), zeroFr, zeroFr]) + check polyDegree(P) == 1 + check not polyIsZero(P) + + test "edge case - domain size 2": + let D = createDomain(2) + check D.domainSize == 2 + check D.logDomainSize == 1 + + let points = enumerateDomain(D) + check points.len == 2 + check isEqualFr(points[0], oneFr) + check isEqualFr(points[1], D.domainGen) + + test "edge case - very large domain": + let D = createDomain(1024) + check D.domainSize == 1024 + check D.logDomainSize == 10 + + let points = enumerateDomain(D) + check points.len == 1024 + let wrapAround = points[1023] * D.domainGen + check isEqualFr(wrapAround, oneFr) + + test "edge case - field element at prime boundary": + let minusOne = minusOneFr + let one = oneFr + let sum = minusOne + one + check isZeroFr(sum) + + test "edge case - batch inversion with single element": + let xs = @[intToFr(5)] + let ys = batchInverseFr(xs) + check ys.len == 1 + check isEqualFr(xs[0] * ys[0], oneFr) + + test "edge case - batch inversion with zero element": + let xs = @[intToFr(1), intToFr(2), intToFr(3)] + let ys = batchInverseFr(xs) + for i in 0..