Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: JSON parser escape support #88

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions circuits/json/language.circom
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,15 @@ template Syntax() {
// signal output SPACE <== 32;
//-Escape-------------------------------------------------------------------------------------//
// - ASCII char: `\`
// signal output ESCAPE <== 92;
signal output ESCAPE <== 92;
//-Number_Remapping---------------------------------------------------------------------------//
signal output NUMBER_START <== 48;
signal output NUMBER_END <== 57;

}

template Command() {
// STATE = [read_write_value, parsing_string, parsing_number]
// STATE = [read_write_value, parsing_string, parsing_number, escape]
signal output START_BRACE[3] <== [1, 0, 0 ]; // Command returned by switch if we hit a start brace `{`
signal output END_BRACE[3] <== [-1, 0, -1 ]; // Command returned by switch if we hit a end brace `}`
signal output START_BRACKET[3] <== [2, 0, 0 ]; // Command returned by switch if we hit a start bracket `[`
Expand Down
18 changes: 15 additions & 3 deletions circuits/json/machine.circom
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,12 @@ template StateUpdate(MAX_STACK_HEIGHT) {
signal input stack[MAX_STACK_HEIGHT][2];
signal input parsing_string;
signal input parsing_number;
signal input escaped;

signal output next_stack[MAX_STACK_HEIGHT][2];
signal output next_parsing_string;
signal output next_parsing_number;
signal output next_escaped;

component Command = Command();
component Syntax = Syntax();
Expand Down Expand Up @@ -88,6 +90,10 @@ template StateUpdate(MAX_STACK_HEIGHT) {
// * read in a quote `"` *
component readQuote = IsEqual();
readQuote.in <== [byte, Syntax.QUOTE];
// * read in a escape `\` *
component readEscape = IsEqual();
readEscape.in <== [byte, Syntax.ESCAPE];

component readOther = IsZero();
readOther.in <== readDelimeter + readNumber.out + readQuote.out;
//--------------------------------------------------------------------------------------------//
Expand Down Expand Up @@ -145,9 +151,15 @@ template StateUpdate(MAX_STACK_HEIGHT) {
newStack.readColon <== readColon.out;
newStack.readComma <== readComma.out;
// * set all the next state of the parser *
next_stack <== newStack.next_stack;
next_parsing_string <== parsing_string + mulMaskAndOut.out[1];
next_parsing_number <== parsing_number + mulMaskAndOut.out[2];
// b * (y - x) + x --> Simple way of doing a switch with boolean b
for(var i = 0 ; i < MAX_STACK_HEIGHT ; i++) {
next_stack[i][0] <== escaped * (stack[i][0] - newStack.next_stack[i][0]) + newStack.next_stack[i][0];
next_stack[i][1] <== escaped * (stack[i][1] - newStack.next_stack[i][1]) + newStack.next_stack[i][1];
}
next_parsing_string <== escaped * (parsing_string - (parsing_string + mulMaskAndOut.out[1])) + (parsing_string + mulMaskAndOut.out[1]);
next_parsing_number <== escaped * (parsing_number - (parsing_number + mulMaskAndOut.out[2])) + (parsing_number + mulMaskAndOut.out[2]);
// Toggle escaped if read
next_escaped <== readEscape.out * (1 - escaped);
//--------------------------------------------------------------------------------------------//
}

Expand Down
22 changes: 22 additions & 0 deletions circuits/json/parser.circom
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,41 @@ template Parser(DATA_BYTES, MAX_STACK_HEIGHT) {
}
State[0].parsing_string <== 0;
State[0].parsing_number <== 0;
State[0].escaped <== 0;

// Debugging
for(var i = 0; i<MAX_STACK_HEIGHT; i++) {
log("State[", 0, "].next_stack[", i,"] = [",State[0].next_stack[i][0], "][", State[0].next_stack[i][1],"]" );
}
log("State[", 0, "].next_parsing_string =", State[0].next_parsing_string);
log("State[", 0, "].next_parsing_number =", State[0].next_parsing_number);
log("State[", 0, "].next_escaped =", State[0].next_escaped);
log("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");

for(var data_idx = 1; data_idx < DATA_BYTES; data_idx++) {
State[data_idx] = StateUpdate(MAX_STACK_HEIGHT);
State[data_idx].byte <== data[data_idx];
State[data_idx].stack <== State[data_idx - 1].next_stack;
State[data_idx].parsing_string <== State[data_idx - 1].next_parsing_string;
State[data_idx].parsing_number <== State[data_idx - 1].next_parsing_number;
State[data_idx].escaped <== State[data_idx - 1].next_escaped;

// Debugging
for(var i = 0; i<MAX_STACK_HEIGHT; i++) {
log("State[", data_idx, "].next_stack[", i,"] = [",State[data_idx].next_stack[i][0], "][", State[data_idx].next_stack[i][1],"]" );
}
log("State[", data_idx, "].next_parsing_string =", State[data_idx].next_parsing_string);
log("State[", data_idx, "].next_parsing_number =", State[data_idx].next_parsing_number);
log("State[", data_idx, "].next_escaped =", State[data_idx].next_escaped);
log("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
}

// Constrain to have valid JSON
State[DATA_BYTES - 1].next_parsing_string === 0;
State[DATA_BYTES - 1].next_parsing_number === 0;
State[DATA_BYTES - 1].next_escaped === 0;
for(var i = 0; i < MAX_STACK_HEIGHT; i++) {
State[DATA_BYTES - 1].next_stack[i] === [0,0];
}

}
19 changes: 17 additions & 2 deletions circuits/test/json/parser.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ describe("JSON Parser", () => {

it(`array only input`, async () => {
let filename = "array_only";
let [input, keyUnicode, output] = readJSONInputFile(`${filename}.json`, [0]);
let [input, keyUnicode, output] = readJSONInputFile(`${filename}.json`, []);

circuit = await circomkit.WitnessTester(`Parser`, {
file: "json/parser",
Expand All @@ -20,7 +20,22 @@ describe("JSON Parser", () => {

it(`object input`, async () => {
let filename = "value_object";
let [input, keyUnicode, output] = readJSONInputFile(`${filename}.json`, ["a"]);
let [input, keyUnicode, output] = readJSONInputFile(`${filename}.json`, []);

circuit = await circomkit.WitnessTester(`Parser`, {
file: "json/parser",
template: "Parser",
params: [input.length, 3],
});

await circuit.expectPass({
data: input
});
});

it(`string_escape input`, async () => {
let filename = "string_escape";
let [input, keyUnicode, output] = readJSONInputFile(`${filename}.json`, []);

circuit = await circomkit.WitnessTester(`Parser`, {
file: "json/parser",
Expand Down
Loading