Skip to content

Commit

Permalink
fixed witness tester AssertionError checks
Browse files Browse the repository at this point in the history
  • Loading branch information
erhant committed Jul 1, 2023
1 parent 952370a commit a0dc8d3
Show file tree
Hide file tree
Showing 3 changed files with 47 additions and 38 deletions.
15 changes: 9 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -178,11 +178,14 @@ describe('witness tester', () => {
});
```

You can check if the number of constraints are correct using `getConstraintCount`, as shown below:
You can check if the number of constraints are correct using `expectConstraintCount`, as shown below:

```ts
it('should have correct number of constraints', async () => {
expect(await circuit.getConstraintCount()).to.eq(NUM_CONSTRAINTS);
// expects at least N constraints
await circuit.expectConstraintCount(N);
// expects exactly N constraints
await circuit.expectConstraintCount(N, true);
});
```

Expand All @@ -198,15 +201,15 @@ it('should compute correctly', async () => {

Finally, you can run tests on the witnesses too. This is most useful when you would like to check for soundness errors.

- `expectConstraintsPass(witness)` checks if constraints are passing for a witness
- `expectConstraintsFail(witness)` checks if constraints are failing
- `expectConstraintPass(witness)` checks if constraints are passing for a witness
- `expectConstraintFail(witness)` checks if constraints are failing

You can compute the witness via the `calculateWitness(input)` function. To test for soundness errors, you may edit the witness and see if constraints are failing. Circomkit provides a nice utility for this purpose, called `editWitness(witness, symbols)`. You simply provide a dictionary of symbols to their new values, and it will edit the witness accordingly. See the example below:

```ts
it('should pass on correct witness', async () => {
const witness = await circuit.calculateWitness(INPUT);
await circuit.expectConstraintsPass(witness);
await circuit.expectConstraintPass(witness);
});

it('should fail on fake witness', async () => {
Expand All @@ -216,7 +219,7 @@ it('should fail on fake witness', async () => {
'main.component.signal': BigInt('0xCAFE'),
'main.foo.bar[0]': BigInt('0b0101'),
});
await circuit.expectConstraintsFail(badWitness);
await circuit.expectConstraintFail(badWitness);
});
```

Expand Down
62 changes: 34 additions & 28 deletions src/testers/witnessTester.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type {WitnessType, CircuitSignals, SymbolsType, SignalValueType} from '../types/circuit';
import type {CircomWasmTester} from '../types/circom_tester';
import {assert, expect} from 'chai';
import {AssertionError, assert, expect} from 'chai';

/** A utility class to test your circuits. Use `expectFail` and `expectPass` to test out evaluations. */
export default class WitnessTester<IN extends readonly string[] = [], OUT extends readonly string[] = []> {
Expand All @@ -15,12 +15,10 @@ export default class WitnessTester<IN extends readonly string[] = [], OUT extend
this.circomWasmTester = circomWasmTester;
}

/**
* Assert that constraints are valid for a given witness.
/** Assert that constraints are valid for a given witness.
* @param witness witness
*/
async expectConstraintsPass(witness: WitnessType): Promise<void> {
// underlying function uses Chai.assert
async expectConstraintPass(witness: WitnessType): Promise<void> {
return this.circomWasmTester.checkConstraints(witness);
}

Expand All @@ -31,18 +29,14 @@ export default class WitnessTester<IN extends readonly string[] = [], OUT extend
* that there are soundness errors in the circuit.
* @param witness witness
*/
async expectConstraintsFail(witness: WitnessType): Promise<void> {
// underlying function uses Chai.assert
try {
await this.expectConstraintsPass(witness);
assert.fail('Expected constraints to not match!');
} catch (err) {
expect((err as Error).message).to.eq("Constraint doesn't match");
}
async expectConstraintFail(witness: WitnessType): Promise<void> {
await this.expectConstraintPass(witness).then(
() => assert.fail('Expected constraints to not match.'),
err => expect(err).to.be.instanceOf(AssertionError)
);
}

/**
* Compute witness given the input signals.
/** Compute witness given the input signals.
* @param input all signals, private and public
*/
async calculateWitness(input: CircuitSignals<IN>): Promise<WitnessType> {
Expand All @@ -58,25 +52,39 @@ export default class WitnessTester<IN extends readonly string[] = [], OUT extend
return numConstraints;
}

/**
* Expect an input to fail an assertion in the circuit.
* @param input bad input
/** Asserts that the circuit has enough constraints.
*
* By default, this function checks if there **at least** `expected` many constraints in the circuit.
* If `exact` option is set to `true`, it will also check if the number of constraints is exactly equal to
* the `expected` amount.
*
* If first check fails, it means the circuit is under-constrained. If the second check fails, it means
* the circuit is over-constrained.
*/
async expectConstraintCount(expected: number, exact?: boolean) {
const count = await this.getConstraintCount();
expect(count, 'Circuit is under-constrained').to.be.greaterThanOrEqual(expected);

if (exact) {
expect(count, 'Circuit is over-constrained').to.eq(expected);
}
}

/** Expect an input to fail an assertion in the circuit. */
async expectFail(input: CircuitSignals<IN>) {
await this.calculateWitness(input).then(
() => assert.fail(),
err => expect(err.message).to.eq('Error: Assert Failed.')
() => assert.fail('Expected witness calculation to fail.'),
err => expect(err).to.be.instanceOf(AssertionError)
);
}

/**
* Expect an input to pass assertions and match the output.
* @param input correct input
* @param output expected output, if `undefined` it will only check constraints
/** Expect an input to pass assertions and match the output.
*
* If `output` is omitted, it will only check for constraints to pass.
*/
async expectPass(input: CircuitSignals<IN>, output?: CircuitSignals<OUT>) {
const witness = await this.calculateWitness(input);
await this.expectConstraintsPass(witness);
await this.expectConstraintPass(witness);
if (output) {
await this.assertOut(witness, output);
}
Expand All @@ -93,14 +101,12 @@ export default class WitnessTester<IN extends readonly string[] = [], OUT extend
* 4. for each signal & it's required symbols, corresponding witness values are retrieved from witness
* 5. the results are aggregated in a final object, of the same type of circuit output signals
*
* @param input input signals
* @param outputSignals an array of signal names
* @returns output signals
*/
async compute(input: CircuitSignals<IN>, outputSignals: OUT): Promise<CircuitSignals<typeof outputSignals>> {
// compute witness & check constraints
const witness = await this.calculateWitness(input);
await this.expectConstraintsPass(witness);
await this.expectConstraintPass(witness);

// get symbols of main component
await this.loadSymbols();
Expand Down
8 changes: 4 additions & 4 deletions tests/testers.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {Circomkit, ProofTester, WitnessTester} from '../src';
import {expect} from 'chai';
import {CIRCUIT_CONFIG, CIRCUIT_NAME, INPUT, N, OUTPUT} from './common';
import {expect} from 'chai';

describe('witness tester', () => {
let circuit: WitnessTester<['in'], ['out']>;
Expand All @@ -14,7 +14,7 @@ describe('witness tester', () => {
});

it('should have correct number of constraints', async () => {
expect(await circuit.getConstraintCount()).to.eq(N);
await circuit.expectConstraintCount(N, true);
});

it('should assert correctly', async () => {
Expand All @@ -29,15 +29,15 @@ describe('witness tester', () => {

it('should assert for correct witness', async () => {
const witness = await circuit.calculateWitness(INPUT);
await circuit.expectConstraintsPass(witness);
await circuit.expectConstraintPass(witness);
});

it('should NOT assert for bad witness', async () => {
const witness = await circuit.calculateWitness(INPUT);
const badWitness = await circuit.editWitness(witness, {
'main.inner[0]': 99999n,
});
await circuit.expectConstraintsFail(badWitness);
await circuit.expectConstraintFail(badWitness);
});
});

Expand Down

0 comments on commit a0dc8d3

Please sign in to comment.