Skip to content

Commit

Permalink
test: added tests for substring-match template
Browse files Browse the repository at this point in the history
  • Loading branch information
shreyas-londhe committed Aug 1, 2024
1 parent d85b22e commit b9008c4
Show file tree
Hide file tree
Showing 3 changed files with 121 additions and 3 deletions.
17 changes: 14 additions & 3 deletions packages/circuits/helpers/substring-match.circom
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,16 @@ pragma circom 2.1.6;
include "circomlib/circuits/comparators.circom";
include "circomlib/circuits/mux1.circom";

/// @title SubstringMatch
/// @notice This template verifies if a given substring exists within a larger string at a specified index
/// @dev Uses a Random Linear Combination (RLC) approach to efficiently compare substrings
/// @param maxLength The maximum length of the input string
/// @param maxSubstringLength The maximum length of the substring to be matched
/// @input in An array of ASCII values representing the input string
/// @input startIndex The starting index of the substring in the input string
/// @input revealedString An array of ASCII values representing the substring to be matched
/// @input r A random value used for the RLC calculation
/// @output isValid A signal that is 1 if the substring matches at the given index, 0 otherwise
template SubstringMatch(maxLength, maxSubstringLength) {
signal input in[maxLength];
signal input startIndex;
Expand All @@ -11,7 +21,7 @@ template SubstringMatch(maxLength, maxSubstringLength) {

// Check if each character in the revealed string is non-zero
signal isNonZero[maxSubstringLength];
component isZero[maxSubstringLength];
signal isZero[maxSubstringLength];
for (var i = 0; i < maxSubstringLength; i++) {
isZero[i] <== IsEqual()([revealedString[i], 0]);
isNonZero[i] <== 1 - isZero[i];
Expand All @@ -34,7 +44,7 @@ template SubstringMatch(maxLength, maxSubstringLength) {

// Create startMask
signal startMask[maxLength];
component startMaskEq[maxLength];
signal startMaskEq[maxLength];
startMaskEq[0] <== IsEqual()([0, startIndex]);
startMask[0] <== startMaskEq[0];
for (var i = 1; i < maxLength; i++) {
Expand All @@ -44,7 +54,7 @@ template SubstringMatch(maxLength, maxSubstringLength) {

// Create endMask
signal endMask[maxLength];
component endMaskEq[maxLength];
signal endMaskEq[maxLength];
endMaskEq[0] <== IsEqual()([0, endIndex]);
endMask[0] <== 1 - endMaskEq[0]; // This will always be 1;
for (var i = 1; i < maxLength; i++) {
Expand Down Expand Up @@ -106,5 +116,6 @@ template SubstringMatch(maxLength, maxSubstringLength) {
}

// Check if RLC for maskedIn is equal to adjusted RLC for revealedString
signal output isValid;
isValid <== IsEqual()([sumMaskedIn[maxLength - 1], sumRevealed[maxSubstringLength - 1]]);
}
102 changes: 102 additions & 0 deletions packages/circuits/tests/substring-match.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import { wasm as wasm_tester } from 'circom_tester';
import path from 'path';

describe('SubstringMatch', () => {
let circuit: any;

beforeAll(async () => {
circuit = await wasm_tester(
path.join(__dirname, './test-circuits/substring-match-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"
r: 69, // A prime number for the random linear combination
};

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)
r: 69,
};

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"
r: 69,
};

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"
r: 69,
};

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"
r: 69,
};

const witness = await circuit.calculateWitness(input);
await circuit.checkConstraints(witness);

await circuit.assertOut(witness, {
isValid: 0,
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pragma circom 2.1.6;

include "../../helpers/substring-match.circom";

component main = SubstringMatch(32, 16);

0 comments on commit b9008c4

Please sign in to comment.