diff --git a/packages/circuits/helpers/reveal-substring.circom b/packages/circuits/helpers/reveal-substring.circom index 8b29d180..871f4a94 100644 --- a/packages/circuits/helpers/reveal-substring.circom +++ b/packages/circuits/helpers/reveal-substring.circom @@ -24,12 +24,12 @@ template RevealSubstring(maxLength, maxSubstringLength) { // substringLength should be less than maxSubstringLength signal lengthCheck; - lengthCheck <== LessThan(log2Ceil(maxSubstringLength))([substringLength, maxSubstringLength]); + lengthCheck <== LessThan(log2Ceil(maxSubstringLength))([substringLength, maxSubstringLength + 1]); lengthCheck === 1; // substringStartIndex + substringLength should be less than maxLength signal startIndexPlusLengthCheck; - startIndexPlusLengthCheck <== LessThan(log2Ceil(maxLength))([substringStartIndex + substringLength, maxLength]); + startIndexPlusLengthCheck <== LessThan(log2Ceil(maxLength))([substringStartIndex + substringLength, maxLength + 1]); startIndexPlusLengthCheck === 1; // Extract the substring diff --git a/packages/circuits/tests/reveal-substring.test.ts b/packages/circuits/tests/reveal-substring.test.ts new file mode 100644 index 00000000..6ce639b9 --- /dev/null +++ b/packages/circuits/tests/reveal-substring.test.ts @@ -0,0 +1,179 @@ +import { wasm as wasm_tester } from "circom_tester"; +import path from "path"; + +describe("RevealSubstring Circuit", () => { + let circuit: any; + + beforeAll(async () => { + circuit = await wasm_tester( + path.join( + __dirname, + "./test-circuits/reveal-substring-test.circom" + ), + { + recompile: true, + include: path.join(__dirname, "../../../node_modules"), + output: path.join(__dirname, "./compiled-test-circuits"), + } + ); + }); + + it("should correctly reveal a substring in the middle of the input", async () => { + const input = { + in: [ + ...Array(100) + .fill(0) + .map((_, i) => (i % 255) + 1), + ...Array(156).fill(0), + ], + substringStartIndex: 50, + substringLength: 5, + }; + const witness = await circuit.calculateWitness(input); + await circuit.checkConstraints(witness); + expect(witness.slice(1, 17)).toEqual([ + 51n, + 52n, + 53n, + 54n, + 55n, + ...Array(11).fill(0n), + ]); + }); + + it("should correctly reveal a substring at the start of the input", async () => { + const input = { + in: [ + ...Array(100) + .fill(0) + .map((_, i) => (i % 255) + 1), + ...Array(156).fill(0), + ], + substringStartIndex: 0, + substringLength: 5, + }; + const witness = await circuit.calculateWitness(input); + await circuit.checkConstraints(witness); + expect(witness.slice(1, 17)).toEqual([ + 1n, + 2n, + 3n, + 4n, + 5n, + ...Array(11).fill(0n), + ]); + }); + + it("should correctly reveal a substring at the end of the non-zero input", async () => { + const input = { + in: [ + ...Array(100) + .fill(0) + .map((_, i) => (i % 255) + 1), + ...Array(156).fill(0), + ], + substringStartIndex: 95, + substringLength: 5, + }; + const witness = await circuit.calculateWitness(input); + await circuit.checkConstraints(witness); + expect(witness.slice(1, 17)).toEqual([ + 96n, + 97n, + 98n, + 99n, + 100n, + ...Array(11).fill(0n), + ]); + }); + + it("should fail when substringStartIndex is out of bounds", async () => { + const input = { + in: Array(256).fill(1), + substringStartIndex: 256, + substringLength: 5, + }; + await expect(circuit.calculateWitness(input)).rejects.toThrow( + "Assert Failed" + ); + }); + + it("should fail when substringLength is greater than maxSubstringLength", async () => { + const input = { + in: Array(256).fill(1), + substringStartIndex: 0, + substringLength: 17, + }; + await expect(circuit.calculateWitness(input)).rejects.toThrow( + "Assert Failed" + ); + }); + + it("should fail when substringStartIndex + substringLength exceeds maxLength", async () => { + const input = { + in: Array(256).fill(1), + substringStartIndex: 250, + substringLength: 7, + }; + await expect(circuit.calculateWitness(input)).rejects.toThrow( + "Assert Failed" + ); + }); + + it("should correctly reveal a substring of length 1", async () => { + const input = { + in: [ + ...Array(100) + .fill(0) + .map((_, i) => (i % 255) + 1), + ...Array(156).fill(0), + ], + substringStartIndex: 50, + substringLength: 1, + }; + const witness = await circuit.calculateWitness(input); + await circuit.checkConstraints(witness); + expect(witness.slice(1, 17)).toEqual([51n, ...Array(15).fill(0n)]); + }); + + it("should correctly reveal the maximum length substring", async () => { + const input = { + in: [ + ...Array(100) + .fill(0) + .map((_, i) => (i % 255) + 1), + ...Array(156).fill(0), + ], + substringStartIndex: 0, + substringLength: 16, + }; + const witness = await circuit.calculateWitness(input); + await circuit.checkConstraints(witness); + expect(witness.slice(1, 17)).toEqual( + Array(16) + .fill(0) + .map((_, i) => BigInt(i + 1)) + ); + }); + + it("should pad with zeros when substringLength is less than maxSubstringLength", async () => { + const input = { + in: [ + ...Array(100) + .fill(0) + .map((_, i) => (i % 255) + 1), + ...Array(156).fill(0), + ], + substringStartIndex: 0, + substringLength: 3, + }; + const witness = await circuit.calculateWitness(input); + await circuit.checkConstraints(witness); + expect(witness.slice(1, 17)).toEqual([ + 1n, + 2n, + 3n, + ...Array(13).fill(0n), + ]); + }); +}); diff --git a/packages/circuits/tests/substring-match.test.ts b/packages/circuits/tests/substring-match.test.ts deleted file mode 100644 index ed146db0..00000000 --- a/packages/circuits/tests/substring-match.test.ts +++ /dev/null @@ -1,97 +0,0 @@ -import { wasm as wasm_tester } from 'circom_tester'; -import path from 'path'; - -describe('Case 1: Substring Match With StartIndex', () => { - let circuit: any; - - beforeAll(async () => { - circuit = await wasm_tester( - path.join(__dirname, './test-circuits/substring-match-with-start-index-test.circom'), - { - recompile: true, - include: path.join(__dirname, '../../../node_modules'), - output: path.join(__dirname, './compiled-test-circuits'), - } - ); - }); - - const padArray = (arr: number[], length: number) => [ - ...arr, - ...Array(length - arr.length).fill(0) - ]; - - it('should correctly match a substring', async () => { - const input = { - in: padArray([104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100], 32), // "hello world" - startIndex: 6, - revealedString: padArray([119, 111, 114, 108, 100], 16), // "world" - }; - - const witness = await circuit.calculateWitness(input); - await circuit.checkConstraints(witness); - - await circuit.assertOut(witness, { - isValid: 1, - }); - }); - - it('should fail when substring does not match', async () => { - const input = { - in: padArray([104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100], 32), // "hello world" - startIndex: 6, - revealedString: padArray([119, 111, 114, 108, 107], 16), // "worlk" (last character different) - }; - - const witness = await circuit.calculateWitness(input); - await circuit.checkConstraints(witness); - - await circuit.assertOut(witness, { - isValid: 0, - }); - }); - - it('should handle matching at the beginning of the string', async () => { - const input = { - in: padArray([104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100], 32), // "hello world" - startIndex: 0, - revealedString: padArray([104, 101, 108, 108, 111], 16), // "hello" - }; - - const witness = await circuit.calculateWitness(input); - await circuit.checkConstraints(witness); - - await circuit.assertOut(witness, { - isValid: 1, - }); - }); - - it('should handle matching at the end of the string', async () => { - const input = { - in: padArray([104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100], 32), // "hello world" - startIndex: 7, - revealedString: padArray([111, 114, 108, 100], 16), // "orld" - }; - - const witness = await circuit.calculateWitness(input); - await circuit.checkConstraints(witness); - - await circuit.assertOut(witness, { - isValid: 1, - }); - }); - - it('should fail when startIndex is out of bounds', async () => { - const input = { - in: padArray([104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100], 32), // "hello world" - startIndex: 32, // Out of bounds (valid indices are 0-31) - revealedString: padArray([100], 16), // "d" - }; - - const witness = await circuit.calculateWitness(input); - await circuit.checkConstraints(witness); - - await circuit.assertOut(witness, { - isValid: 0, - }); - }); -}); \ No newline at end of file diff --git a/packages/circuits/tests/test-circuits/reveal-substring-test.circom b/packages/circuits/tests/test-circuits/reveal-substring-test.circom new file mode 100644 index 00000000..940cc983 --- /dev/null +++ b/packages/circuits/tests/test-circuits/reveal-substring-test.circom @@ -0,0 +1,5 @@ +pragma circom 2.1.6; + +include "../../helpers/reveal-substring.circom"; + +component main = RevealSubstring(256, 16); diff --git a/packages/circuits/tests/test-circuits/substring-match-with-start-index-test.circom b/packages/circuits/tests/test-circuits/substring-match-with-start-index-test.circom deleted file mode 100644 index da47d55f..00000000 --- a/packages/circuits/tests/test-circuits/substring-match-with-start-index-test.circom +++ /dev/null @@ -1,5 +0,0 @@ -pragma circom 2.1.6; - -include "../../helpers/substring-match.circom"; - -component main = SubstringMatchWithStartIndex(32, 16);