Skip to content

Commit

Permalink
Feature/constraints testing (#5)
Browse files Browse the repository at this point in the history
* Add constraints testing

* Change project structure

* Update version

* update readme

---------

Co-authored-by: Artem Chystiakov <[email protected]>
  • Loading branch information
Hrom131 and Arvolear authored Aug 15, 2024
1 parent 3bba7f9 commit fab310c
Show file tree
Hide file tree
Showing 12 changed files with 386 additions and 9 deletions.
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,20 @@ await expect(matrix).to.not.verifyProof(otherProof);
await expect(matrix).to.useSolidityVerifier(matrixVerifier).and.verifyProof(proof);
```

### Constraints testing

```ts
const matrix = await zkit.getCircuit("Matrix");

// constraints > 6
expect(matrix).to.have.constraints.gt(6);
expect(matrix).to.have.constraints.greaterThan(6);

// constraints < 10
expect(matrix).to.have.constraints.lt(10);
expect(matrix).to.have.constraints.lessThan(10);
```

## Known limitations

- Do not use `not` chai negation prior `witnessInputs` call, this will break the typization.
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@solarity/chai-zkit",
"version": "0.2.0",
"version": "0.2.1",
"license": "MIT",
"author": "Distributed Lab",
"readme": "README.md",
Expand Down
4 changes: 2 additions & 2 deletions src/chai-zkit.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { witness } from "./witness";
import { proof } from "./proof";
import { witness, proof, constraints } from "./core";

export function chaiZkit(chai: Chai.ChaiStatic, utils: Chai.ChaiUtils): void {
witness(chai, utils);
proof(chai, utils);
constraints(chai, utils);
}
169 changes: 169 additions & 0 deletions src/core/constraints.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
import { getConstraintsNumber, isCircuitZKit } from "../utils";

export function constraints(chai: Chai.ChaiStatic, utils: Chai.ChaiUtils): void {
chai.Assertion.addProperty("constraints", function (this: any) {
utils.flag(this, "constraints", true);

return this;
});

chai.Assertion.overwriteMethod("above", function (_super) {
return function (this: any, num: number | Date) {
const obj = utils.flag(this, "object");

if (isCircuitZKit(obj) && utils.flag(this, "constraints")) {
const newAssertation = new chai.Assertion(getConstraintsNumber(obj.mustGetArtifactsFilePath("r1cs")));

utils.flag(newAssertation, "negate", utils.flag(this, "negate"));

newAssertation.to.above(num);
} else {
_super.apply(this, arguments);
}
};
});

chai.Assertion.overwriteMethod("gt", function (_super) {
return function (this: any, num: number | Date) {
const obj = utils.flag(this, "object");

if (isCircuitZKit(obj) && utils.flag(this, "constraints")) {
const newAssertation = new chai.Assertion(getConstraintsNumber(obj.mustGetArtifactsFilePath("r1cs")));

utils.flag(newAssertation, "negate", utils.flag(this, "negate"));

newAssertation.to.gt(num);
} else {
_super.apply(this, arguments);
}
};
});

chai.Assertion.overwriteMethod("greaterThan", function (_super) {
return function (this: any, num: number | Date) {
const obj = utils.flag(this, "object");

if (isCircuitZKit(obj) && utils.flag(this, "constraints")) {
const newAssertation = new chai.Assertion(getConstraintsNumber(obj.mustGetArtifactsFilePath("r1cs")));

utils.flag(newAssertation, "negate", utils.flag(this, "negate"));

newAssertation.to.greaterThan(num);
} else {
_super.apply(this, arguments);
}
};
});

chai.Assertion.overwriteMethod("below", function (_super) {
return function (this: any, num: number | Date) {
const obj = utils.flag(this, "object");

if (isCircuitZKit(obj) && utils.flag(this, "constraints")) {
const newAssertation = new chai.Assertion(getConstraintsNumber(obj.mustGetArtifactsFilePath("r1cs")));

utils.flag(newAssertation, "negate", utils.flag(this, "negate"));

newAssertation.to.below(num);
} else {
_super.apply(this, arguments);
}
};
});

chai.Assertion.overwriteMethod("lt", function (_super) {
return function (this: any, num: number | Date) {
const obj = utils.flag(this, "object");

if (isCircuitZKit(obj) && utils.flag(this, "constraints")) {
const newAssertation = new chai.Assertion(getConstraintsNumber(obj.mustGetArtifactsFilePath("r1cs")));

utils.flag(newAssertation, "negate", utils.flag(this, "negate"));

newAssertation.to.lt(num);
} else {
_super.apply(this, arguments);
}
};
});

chai.Assertion.overwriteMethod("lessThan", function (_super) {
return function (this: any, num: number | Date) {
const obj = utils.flag(this, "object");

if (isCircuitZKit(obj) && utils.flag(this, "constraints")) {
const newAssertation = new chai.Assertion(getConstraintsNumber(obj.mustGetArtifactsFilePath("r1cs")));

utils.flag(newAssertation, "negate", utils.flag(this, "negate"));

newAssertation.to.lessThan(num);
} else {
_super.apply(this, arguments);
}
};
});

chai.Assertion.overwriteMethod("most", function (_super) {
return function (this: any, num: number | Date) {
const obj = utils.flag(this, "object");

if (isCircuitZKit(obj) && utils.flag(this, "constraints")) {
const newAssertation = new chai.Assertion(getConstraintsNumber(obj.mustGetArtifactsFilePath("r1cs")));

utils.flag(newAssertation, "negate", utils.flag(this, "negate"));

newAssertation.to.most(num);
} else {
_super.apply(this, arguments);
}
};
});

chai.Assertion.overwriteMethod("lte", function (_super) {
return function (this: any, num: number | Date) {
const obj = utils.flag(this, "object");

if (isCircuitZKit(obj) && utils.flag(this, "constraints")) {
const newAssertation = new chai.Assertion(getConstraintsNumber(obj.mustGetArtifactsFilePath("r1cs")));

utils.flag(newAssertation, "negate", utils.flag(this, "negate"));

newAssertation.to.lte(num);
} else {
_super.apply(this, arguments);
}
};
});

chai.Assertion.overwriteMethod("lessThanOrEqual", function (_super) {
return function (this: any, num: number | Date) {
const obj = utils.flag(this, "object");

if (isCircuitZKit(obj) && utils.flag(this, "constraints")) {
const newAssertation = new chai.Assertion(getConstraintsNumber(obj.mustGetArtifactsFilePath("r1cs")));

utils.flag(newAssertation, "negate", utils.flag(this, "negate"));

newAssertation.to.lessThanOrEqual(num);
} else {
_super.apply(this, arguments);
}
};
});

chai.Assertion.overwriteMethod("within", function (_super) {
return function (this: any, start: number, finish: number) {
const obj = utils.flag(this, "object");

if (isCircuitZKit(obj) && utils.flag(this, "constraints")) {
const newAssertation = new chai.Assertion(getConstraintsNumber(obj.mustGetArtifactsFilePath("r1cs")));

utils.flag(newAssertation, "negate", utils.flag(this, "negate"));

newAssertation.to.within(start, finish);
} else {
_super.apply(this, arguments);
}
};
});
}
3 changes: 3 additions & 0 deletions src/core/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from "./proof";
export * from "./witness";
export * from "./constraints";
4 changes: 2 additions & 2 deletions src/proof.ts → src/core/proof.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Signals } from "@solarity/zkit";

import { GENERATE_PROOF_METHOD, USE_SOLIDITY_VERIFIER_METHOD, VERIFY_PROOF_METHOD } from "./constants";
import { checkCircuitZKit } from "./utils";
import { GENERATE_PROOF_METHOD, USE_SOLIDITY_VERIFIER_METHOD, VERIFY_PROOF_METHOD } from "../constants";
import { checkCircuitZKit } from "../utils";

export function proof(chai: Chai.ChaiStatic, utils: Chai.ChaiUtils): void {
chai.Assertion.addMethod(GENERATE_PROOF_METHOD, function (this: any, inputs: Signals) {
Expand Down
4 changes: 2 additions & 2 deletions src/witness.ts → src/core/witness.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { CircuitZKit, NumberLike, Signals } from "@solarity/zkit";

import { checkCircuitZKit, loadOutputs, outputSignalsCompare } from "./utils";
import { STRICT_PROPERTY, WITNESS_INPUTS_METHOD, WITNESS_OUTPUTS_METHOD } from "./constants";
import { STRICT_PROPERTY, WITNESS_INPUTS_METHOD, WITNESS_OUTPUTS_METHOD } from "../constants";
import { checkCircuitZKit, loadOutputs, outputSignalsCompare } from "../utils";

export function witness(chai: Chai.ChaiStatic, utils: Chai.ChaiUtils): void {
chai.Assertion.addProperty(STRICT_PROPERTY, function (this: any) {
Expand Down
2 changes: 2 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ declare global {
interface AsyncAssertion<T = any> extends Promise<void>, Witness<T>, Proof<T> {
not: AsyncAssertion<T>;
strict: AsyncAssertion<T>;
constraints: AsyncAssertion<T>;
to: AsyncAssertion<T>;
be: AsyncAssertion<T>;
been: AsyncAssertion<T>;
Expand All @@ -53,6 +54,7 @@ declare global {
}

interface Assertion<T = any> extends Witness<T>, Proof<T> {
constraints: Assertion<T>;
to: Assertion<T>;
be: Assertion<T>;
been: Assertion<T>;
Expand Down
4 changes: 4 additions & 0 deletions src/utils/compare-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ export function checkCircuitZKit(circuitZKit: any, methodName: string) {
}
}

export function isCircuitZKit(circuitZKit: any): circuitZKit is CircuitZKit {
return circuitZKit instanceof CircuitZKit;
}

function compareSignals(actualSignal: Signal, expectedSignal: Signal): boolean {
const actualSignalValues: NumberLike[] = flattenSignal(actualSignal);
const expectedSignalValues: NumberLike[] = flattenSignal(expectedSignal);
Expand Down
32 changes: 32 additions & 0 deletions src/utils/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,38 @@ export function stringifySignal(signal: Signal): string {
return JSON.stringify(signal, (_, v) => (typeof v === "bigint" ? v.toString() : v)).replaceAll(`"`, "");
}

export function getConstraintsNumber(r1csFilePath: string): number {
const r1csDescriptor = fs.openSync(r1csFilePath, "r");

const readBytes = (position: number, length: number): bigint => {
const buffer = Buffer.alloc(length);

fs.readSync(r1csDescriptor, buffer, { length, position });

return BigInt(`0x${buffer.reverse().toString("hex")}`);
};

/// @dev https://github.com/iden3/r1csfile/blob/d82959da1f88fbd06db0407051fde94afbf8824a/doc/r1cs_bin_format.md#format-of-the-file
const numberOfSections = readBytes(8, 4);
let sectionStart = 12;

for (let i = 0; i < numberOfSections; ++i) {
const sectionType = Number(readBytes(sectionStart, 4));
const sectionSize = Number(readBytes(sectionStart + 4, 8));

/// @dev Reading header section
if (sectionType == 1) {
const totalConstraintsOffset = 4 + 8 + 4 + 32 + 4 + 4 + 4 + 4 + 8;

return Number(readBytes(sectionStart + totalConstraintsOffset, 4));
}

sectionStart += 4 + 8 + sectionSize;
}

throw new Error(`Header section in ${r1csFilePath} file is not found.`);
}

function loadSym(zkit: CircuitZKit): Map<string, number> {
const symFile = zkit.mustGetArtifactsFilePath("sym");
const signals = new Map<string, number>();
Expand Down
Loading

0 comments on commit fab310c

Please sign in to comment.