diff --git a/compiler/gen.js b/compiler/gen.js index de0e205..23e7bf8 100644 --- a/compiler/gen.js +++ b/compiler/gen.js @@ -207,7 +207,7 @@ async function generateCircuit(regex, circuitLibPath) { const outputPath = `${__dirname}/../build/compiled.circom`; await fs.writeFile(outputPath, tpl); - console.log(`Circuit compiled to ${path.normalize(outputPath)}`); + process.env.DEBUG && console.log(`Circuit compiled to ${path.normalize(outputPath)}`); } catch (error) { console.log(error) } diff --git a/compiler/tpl.circom b/compiler/tpl.circom index af9ae35..69c59bb 100644 --- a/compiler/tpl.circom +++ b/compiler/tpl.circom @@ -2,10 +2,14 @@ pragma circom 2.0.3; include "CIRCUIT_FOLDER/regex_helpers.circom"; -template TEMPLATE_NAME_PLACEHOLDER (msg_bytes) { +template TEMPLATE_NAME_PLACEHOLDER (msg_bytes, reveal_bytes) { signal input msg[msg_bytes]; + signal input match_idx; + signal output start_idx; signal output out; + signal output reveal_shifted[reveal_bytes][msg_bytes]; + var num_bytes = msg_bytes; signal in[num_bytes]; for (var i = 0; i < msg_bytes; i++) { @@ -14,35 +18,63 @@ template TEMPLATE_NAME_PLACEHOLDER (msg_bytes) { COMPILED_CONTENT_PLACEHOLDER + // a flag to indicate the start position of the match + var start_index = 0; + // for counting the number of matches + var count = 0; + // lengths to be consistent with states signal - component check_cur[num_bytes + 1]; component check_start[num_bytes + 1]; - signal states_count[num_bytes + 1]; - var count = 0; + component check_match[num_bytes + 1]; + component check_start_index[num_bytes + 1]; + component matched_idx_eq[msg_bytes]; - // counting the matches by deterining the start positions of the matches - // note that the valid index of states signal starts from 1 for (var i = 0; i < num_bytes; i++) { if (i == 0) { - check_cur[i] = IsEqual(); - check_cur[i].in[0] <== states[1][1]; - check_cur[i].in[1] <== 1; - count += states[1][1]; } else { - check_cur[i] = IsEqual(); - check_cur[i].in[0] <== states[i + 1][1]; - check_cur[i].in[1] <== 1; - check_start[i] = AND(); - check_start[i].a <== check_cur[i].out; - check_start[i].b <== 1 - check_cur[i-1].out; + check_start[i].a <== states[i + 1][1]; + check_start[i].b <== 1 - states[i][1]; count += check_start[i].out; + + check_match[i] = IsEqual(); + check_match[i].in[0] <== count; + check_match[i].in[1] <== match_idx; + + check_start_index[i] = AND(); + check_start_index[i].a <== check_match[i].out; + check_start_index[i].b <== check_start[i].out; + start_index += check_start_index[i].out * i; + } + + matched_idx_eq[i] = IsEqual(); + matched_idx_eq[i].in[0] <== states[i + 1][1] * count; + matched_idx_eq[i].in[1] <== match_idx; + } + + component match_start_index[msg_bytes]; + for (var i = 0; i < msg_bytes; i++) { + match_start_index[i] = IsEqual(); + match_start_index[i].in[0] <== i; + match_start_index[i].in[1] <== start_index; + } + + signal reveal_match[msg_bytes]; + for (var i = 0; i < msg_bytes; i++) { + reveal_match[i] <== matched_idx_eq[i].out * reveal[i]; + } + + for (var j = 0; j < reveal_bytes; j++) { + reveal_shifted[j][j] <== 0; + for (var i = j + 1; i < msg_bytes; i++) { + // This shifts matched string back to the beginning. + reveal_shifted[j][i] <== reveal_shifted[j][i - 1] + match_start_index[i-j].out * reveal_match[i]; } - states_count[i] <== states[i + 1][1] * count; } out <== count; + start_idx <== start_index; } diff --git a/test/circuits/test_regex_compiler.circom b/test/circuits/test_regex_compiler.circom index ae27146..aa33009 100644 --- a/test/circuits/test_regex_compiler.circom +++ b/test/circuits/test_regex_compiler.circom @@ -4,6 +4,7 @@ include "../../build/compiled.circom"; component main { public [ - msg + msg, + match_idx ] -} = Regex(1536); +} = Regex(1536, 44); diff --git a/test/regex-compiler.test.ts b/test/regex-compiler.test.ts index bca725c..96d08bc 100644 --- a/test/regex-compiler.test.ts +++ b/test/regex-compiler.test.ts @@ -16,37 +16,44 @@ describe("regex compiler tests", function () { [ [ - 'matches in the middle', - `email was meant for @(${generator.word_char}+)`, + '1st match in the middle', + [`email was meant for @(${generator.word_char}+)`, 1], (signals: any) => { - for (let i = 28; i <= 32; i++) { - expect(signals.main.states_count[i]).to.equal(1n) + const to_reveal = 'katat'.split('').map((x: any) => BigInt(x.charCodeAt(0))) + for (let m in signals.main.reveal_shifted) { + const index = signals.main.reveal_shifted[m] + const last_pos = index.length - 1 + if (to_reveal[m as any]) { + expect(index[last_pos]).to.equal(to_reveal[m as any]) + } } - for (let i = 67; i <= 71; i++) { - expect(signals.main.states_count[i]).to.equal(2n) + expect(signals.main.out).to.equal(2n) + expect(signals.main.start_idx).to.equal(28n) + } + ], + [ + '2nd match in the middle', + [`email was meant for @(${generator.word_char}+)`, 2], + (signals: any) => { + const to_reveal = 'katat'.split('').map((x: any) => BigInt(x.charCodeAt(0))) + for (let m in signals.main.reveal_shifted) { + const index = signals.main.reveal_shifted[m] + const last_pos = index.length - 1 + if (to_reveal[m as any]) { + expect(index[last_pos]).to.equal(to_reveal[m as any]) + } } expect(signals.main.out).to.equal(2n) + expect(signals.main.start_idx).to.equal(67n) } ], - // [ - // 'match at the beginning', - // `@${generator.word_char}+ email was meant`, - // (signals: any) => { - // console.log(signals.main.states_count) - // for (let i = 0; i < 6; i++) { - // expect(signals.main.states_count[i]).to.equal(1n) - // } - // // for (let i = 67; i <= 71; i++) { - // // expect(signals.main.states_count[i]).to.equal(2n) - // // } - // expect(signals.main.out).to.equal(2n) - // } - // ], ].forEach((test) => { //@ts-ignore const name: string = test[0] //@ts-ignore - const regex: string = test[1] + const regex: string = test[1][0] + //@ts-ignore + const match_idx: number = test[1][1] //@ts-ignore const checkSignals: Function = test[2] @@ -64,7 +71,7 @@ describe("regex compiler tests", function () { }); it('checks witness', async function() { - let witness = await circuit.calculateWitness({msg: in_body_padded}); + let witness = await circuit.calculateWitness({msg: in_body_padded, match_idx}); const signals = await circuit.getJSONOutput('main', witness); checkSignals(signals) await circuit.checkConstraints(witness);