diff --git a/packages/circuits/tests/select-regex-reveal.test.ts b/packages/circuits/tests/select-regex-reveal.test.ts new file mode 100644 index 00000000..fdf55115 --- /dev/null +++ b/packages/circuits/tests/select-regex-reveal.test.ts @@ -0,0 +1,117 @@ +import { wasm } from "circom_tester"; +import path from "path"; + + +describe("Select Regex Reveal", () => { + jest.setTimeout(10 * 60 * 1000); // 10 minutes + + let circuit: any; + + beforeAll(async () => { + circuit = await wasm( + path.join(__dirname, "./test-circuits/select-regex-reveal-test.circom"), + { + recompile: true, + include: path.join(__dirname, "../../../node_modules"), + } + ); + }); + + it("should reveal the substring with maximum revealed length", async function () { + let input = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + const startIndex = Math.floor(Math.random() * 24); + const revealed = Array.from("zk email").map(char => char.charCodeAt(0)); + for (let i = 0; i < revealed.length; i++) { + input[startIndex + i] = revealed[i]; + } + const witness = await circuit.calculateWitness({ + in: input, + startIndex: startIndex, + }); + await circuit.checkConstraints(witness); + await circuit.assertOut(witness, { out: revealed }) + }); + + it("should reveal the substring with non-maximum revealed length", async function () { + let input = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + const startIndex = 30; + const revealed = Array.from("zk").map(char => char.charCodeAt(0)); + for (let i = 0; i < revealed.length; i++) { + input[startIndex + i] = revealed[i]; + } + const witness = await circuit.calculateWitness({ + in: input, + startIndex: startIndex, + }); + await circuit.checkConstraints(witness); + await circuit.assertOut(witness, { out: revealed.concat([0, 0, 0, 0, 0, 0]) }) + }); + + it("should fail when all zero", async function () { + let input = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + const startIndex = Math.floor(Math.random() * 32); + try { + const witness = await circuit.calculateWitness({ + in: input, + startIndex: startIndex, + }); + await circuit.checkConstraints(witness); + } catch (error) { + expect((error as Error).message).toMatch("Assert Failed"); + } + }); + + it("should fail when startIndex is 0", async function () { + let input = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + const startIndex = 1 + Math.floor(Math.random() * 24); + const revealed = Array.from("zk email").map(char => char.charCodeAt(0)); + for (let i = 0; i < revealed.length; i++) { + input[startIndex + i] = revealed[i]; + } + try { + const witness = await circuit.calculateWitness({ + in: input, + startIndex: startIndex - 1, + }); + await circuit.checkConstraints(witness); + } catch (error) { + expect((error as Error).message).toMatch("Assert Failed"); + } + }); + + it("should fail when startIndex is not before 0", async function () { + let input = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + const startIndex = Math.floor(Math.random() * 23); + const revealed = Array.from("zk email").map(char => char.charCodeAt(0)); + for (let i = 0; i < revealed.length; i++) { + input[startIndex + i] = revealed[i]; + } + try { + const witness = await circuit.calculateWitness({ + in: input, + startIndex: startIndex + 1, + }); + await circuit.checkConstraints(witness); + } catch (error) { + expect((error as Error).message).toMatch("Assert Failed"); + } + }); + + it("should fail when startIndex is larger than max length", async function () { + let input = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + const startIndex = Math.floor(Math.random() * 24); + const revealed = Array.from("zk email").map(char => char.charCodeAt(0)); + for (let i = 0; i < revealed.length; i++) { + input[startIndex + i] = revealed[i]; + } + try { + const witness = await circuit.calculateWitness({ + in: input, + startIndex: 32 + }); + await circuit.checkConstraints(witness); + } catch (error) { + expect((error as Error).message).toMatch("Assert Failed"); + } + }); +}); diff --git a/packages/circuits/tests/test-circuits/select-regex-reveal-test.circom b/packages/circuits/tests/test-circuits/select-regex-reveal-test.circom new file mode 100644 index 00000000..1ae9a8c0 --- /dev/null +++ b/packages/circuits/tests/test-circuits/select-regex-reveal-test.circom @@ -0,0 +1,5 @@ +pragma circom 2.1.6; + +include "../../utils/regex.circom"; + +component main = SelectRegexReveal(32,8); \ No newline at end of file diff --git a/packages/circuits/utils/regex.circom b/packages/circuits/utils/regex.circom index 470b1c3e..dd7ef74b 100644 --- a/packages/circuits/utils/regex.circom +++ b/packages/circuits/utils/regex.circom @@ -7,6 +7,7 @@ include "./bytes.circom"; /// @title SelectRegexReveal /// @notice Returns reveal bytes of a regex match from the input /// @notice Verifies data before and after (maxRevealLen) reveal part is zero +/// @notice Assumes that there is only one consecutive sequence of non-zero bytes in `in`. /// @param maxArrayLen Maximum length of the input array /// @param maxRevealLen Maximum length of the reveal part /// @input in Input array; assumes elements to be bytes @@ -26,6 +27,10 @@ template SelectRegexReveal(maxArrayLen, maxRevealLen) { signal isPreviousZero[maxArrayLen]; signal isAboveMaxRevealLen[maxArrayLen]; + // Assert startIndex < maxArrayLen + signal isValidStartIndex <== LessThan(bitLength)([startIndex, maxArrayLen]); + isValidStartIndex === 1; + isPreviousZero[0] <== 1; for(var i = 0; i < maxArrayLen; i++) { isStartIndex[i] <== IsEqual()([i, startIndex]);