diff --git a/circuits/http/extractor.circom b/circuits/http/extractor.circom index d7106f5..a4fa227 100644 --- a/circuits/http/extractor.circom +++ b/circuits/http/extractor.circom @@ -1,7 +1,11 @@ pragma circom 2.1.9; -include "../utils/bytes.circom"; +include "interpreter.circom"; include "parser/machine.circom"; +include "../utils/bytes.circom"; +include "../utils/search.circom"; +include "circomlib/circuits/mux1.circom"; +include "circomlib/circuits/gates.circom"; include "@zk-email/circuits/utils/array.circom"; // TODO: @@ -24,6 +28,8 @@ template ExtractResponse(DATA_BYTES, maxContentLength) { State[0].byte <== data[0]; State[0].parsing_start <== 1; State[0].parsing_header <== 0; + State[0].parsing_field_name <== 0; + State[0].parsing_field_value <== 0; State[0].parsing_body <== 0; State[0].line_status <== 0; @@ -35,6 +41,8 @@ template ExtractResponse(DATA_BYTES, maxContentLength) { State[data_idx].byte <== data[data_idx]; State[data_idx].parsing_start <== State[data_idx - 1].next_parsing_start; State[data_idx].parsing_header <== State[data_idx - 1].next_parsing_header; + State[data_idx].parsing_field_name <== State[data_idx-1].next_parsing_field_name; + State[data_idx].parsing_field_value <== State[data_idx-1].next_parsing_field_value; State[data_idx].parsing_body <== State[data_idx - 1].next_parsing_body; State[data_idx].line_status <== State[data_idx - 1].next_line_status; @@ -42,18 +50,22 @@ template ExtractResponse(DATA_BYTES, maxContentLength) { dataMask[data_idx] <== data[data_idx] * State[data_idx].next_parsing_body; // Debugging - log("State[", data_idx, "].parsing_start ", "= ", State[data_idx].parsing_start); - log("State[", data_idx, "].parsing_header", "= ", State[data_idx].parsing_header); - log("State[", data_idx, "].parsing_body ", "= ", State[data_idx].parsing_body); - log("State[", data_idx, "].line_status ", "= ", State[data_idx].line_status); + log("State[", data_idx, "].parsing_start ", "= ", State[data_idx].parsing_start); + log("State[", data_idx, "].parsing_header ", "= ", State[data_idx].parsing_header); + log("State[", data_idx, "].parsing_field_name ", "= ", State[data_idx].parsing_field_name); + log("State[", data_idx, "].parsing_field_value", "= ", State[data_idx].parsing_field_value); + log("State[", data_idx, "].parsing_body ", "= ", State[data_idx].parsing_body); + log("State[", data_idx, "].line_status ", "= ", State[data_idx].line_status); log("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"); } // Debugging - log("State[", DATA_BYTES, "].parsing_start ", "= ", State[DATA_BYTES-1].next_parsing_start); - log("State[", DATA_BYTES, "].parsing_header", "= ", State[DATA_BYTES-1].next_parsing_header); - log("State[", DATA_BYTES, "].parsing_body ", "= ", State[DATA_BYTES-1].next_parsing_body); - log("State[", DATA_BYTES, "].line_status ", "= ", State[DATA_BYTES-1].next_line_status); + log("State[", DATA_BYTES, "].parsing_start ", "= ", State[DATA_BYTES-1].next_parsing_start); + log("State[", DATA_BYTES, "].parsing_header ", "= ", State[DATA_BYTES-1].next_parsing_header); + log("State[", DATA_BYTES, "].parsing_field_name ", "= ", State[DATA_BYTES-1].parsing_field_name); + log("State[", DATA_BYTES, "].parsing_field_value", "= ", State[DATA_BYTES-1].parsing_field_value); + log("State[", DATA_BYTES, "].parsing_body ", "= ", State[DATA_BYTES-1].next_parsing_body); + log("State[", DATA_BYTES, "].line_status ", "= ", State[DATA_BYTES-1].next_line_status); log("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"); signal valueStartingIndex[DATA_BYTES]; @@ -68,4 +80,87 @@ template ExtractResponse(DATA_BYTES, maxContentLength) { } response <== SelectSubArray(DATA_BYTES, maxContentLength)(dataMask, valueStartingIndex[DATA_BYTES-1]+1, DATA_BYTES - valueStartingIndex[DATA_BYTES-1]); +} + +template ExtractHeaderValue(DATA_BYTES, headerNameLength, maxValueLength) { + signal input data[DATA_BYTES]; + signal input header[headerNameLength]; + + signal output value[maxValueLength]; + + //--------------------------------------------------------------------------------------------// + //-CONSTRAINTS--------------------------------------------------------------------------------// + //--------------------------------------------------------------------------------------------// + component dataASCII = ASCII(DATA_BYTES); + dataASCII.in <== data; + //--------------------------------------------------------------------------------------------// + + // Initialze the parser + component State[DATA_BYTES]; + State[0] = StateUpdate(); + State[0].byte <== data[0]; + State[0].parsing_start <== 1; + State[0].parsing_header <== 0; + State[0].parsing_field_name <== 0; + State[0].parsing_field_value <== 0; + State[0].parsing_body <== 0; + State[0].line_status <== 0; + + signal headerMatch[DATA_BYTES]; + headerMatch[0] <== 0; + signal isHeaderNameMatch[DATA_BYTES]; + isHeaderNameMatch[0] <== 0; + signal readCRLF[DATA_BYTES]; + readCRLF[0] <== 0; + signal valueMask[DATA_BYTES]; + valueMask[0] <== 0; + + for(var data_idx = 1; data_idx < DATA_BYTES; data_idx++) { + State[data_idx] = StateUpdate(); + State[data_idx].byte <== data[data_idx]; + State[data_idx].parsing_start <== State[data_idx - 1].next_parsing_start; + State[data_idx].parsing_header <== State[data_idx - 1].next_parsing_header; + State[data_idx].parsing_field_name <== State[data_idx-1].next_parsing_field_name; + State[data_idx].parsing_field_value <== State[data_idx-1].next_parsing_field_value; + State[data_idx].parsing_body <== State[data_idx - 1].next_parsing_body; + State[data_idx].line_status <== State[data_idx - 1].next_line_status; + + // apply value mask to data + // TODO: change r + headerMatch[data_idx] <== HeaderFieldNameMatch(DATA_BYTES, headerNameLength)(data, header, 100, data_idx); + readCRLF[data_idx] <== IsEqual()([State[data_idx].line_status, 2]); + isHeaderNameMatch[data_idx] <== Mux1()([isHeaderNameMatch[data_idx-1] * (1-readCRLF[data_idx]), 1], headerMatch[data_idx]); + valueMask[data_idx] <== MultiAND(3)([data[data_idx], isHeaderNameMatch[data_idx], State[data_idx].parsing_field_value]); + + // Debugging + log("State[", data_idx, "].parsing_start ", "= ", State[data_idx].parsing_start); + log("State[", data_idx, "].parsing_header ", "= ", State[data_idx].parsing_header); + log("State[", data_idx, "].parsing_field_name ", "= ", State[data_idx].parsing_field_name); + log("State[", data_idx, "].parsing_field_value", "= ", State[data_idx].parsing_field_value); + log("State[", data_idx, "].parsing_body ", "= ", State[data_idx].parsing_body); + log("State[", data_idx, "].line_status ", "= ", State[data_idx].line_status); + log("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"); + } + + // Debugging + log("State[", DATA_BYTES, "].parsing_start ", "= ", State[DATA_BYTES-1].next_parsing_start); + log("State[", DATA_BYTES, "].parsing_header ", "= ", State[DATA_BYTES-1].next_parsing_header); + log("State[", DATA_BYTES, "].parsing_field_name ", "= ", State[DATA_BYTES-1].parsing_field_name); + log("State[", DATA_BYTES, "].parsing_field_value", "= ", State[DATA_BYTES-1].parsing_field_value); + log("State[", DATA_BYTES, "].parsing_body ", "= ", State[DATA_BYTES-1].next_parsing_body); + log("State[", DATA_BYTES, "].line_status ", "= ", State[DATA_BYTES-1].next_line_status); + log("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"); + + signal valueStartingIndex[DATA_BYTES]; + signal isZeroMask[DATA_BYTES]; + signal isPrevStartingIndex[DATA_BYTES]; + valueStartingIndex[0] <== 0; + isZeroMask[0] <== IsZero()(valueMask[0]); + for (var i=1 ; i body CRLF +// - header value parsing doesn't handle SPACE between colon and actual value template StateChange() { signal input readCRLF; signal input readCRLFCRLF; - signal input state[3]; - signal output out[3]; + signal input readSP; + signal input readColon; + signal input state[5]; + signal output out[5]; + // GreaterEqThan(2) because start line can have at most 3 values for request or response + signal isParsingStart <== GreaterEqThan(2)([state[0], 1]); + // increment parsing start counter on reading SP + signal incrementParsingStart <== readSP * isParsingStart; + // disable parsing start on reading CRLF signal disableParsingStart <== readCRLF * state[0]; + + // enable parsing header on reading CRLF + signal enableParsingHeader <== readCRLF * isParsingStart; + // check if we are parsing header + signal isParsingHeader <== GreaterEqThan(10)([state[1], 1]); + // increment parsing header counter on CRLF and parsing header + signal incrementParsingHeader <== readCRLF * isParsingHeader; + // disable parsing header on reading CRLF-CRLF signal disableParsingHeader <== readCRLFCRLF * state[1]; + // parsing field value when parsing header and read Colon `:` + signal isParsingFieldValue <== isParsingHeader * readColon; + + // parsing body when reading CRLF-CRLF and parsing header + signal enableParsingBody <== readCRLFCRLF * isParsingHeader; - out <== [-disableParsingStart, disableParsingStart - disableParsingHeader, disableParsingHeader]; + // parsing_start = out[0] = enable header (default 1) + increment start - disable start + // parsing_header = out[1] = enable header + increment header - disable header + // parsing_field_name = out[2] = enable header + increment header - parsing field value - parsing body + // parsing_field_value = out[3] = parsing field value - increment parsing header (zeroed every time new header starts) + // parsing_body = out[4] = enable body + out <== [incrementParsingStart - disableParsingStart, enableParsingHeader + incrementParsingHeader - disableParsingHeader, enableParsingHeader + incrementParsingHeader - isParsingFieldValue - enableParsingBody, isParsingFieldValue - incrementParsingHeader, enableParsingBody]; } \ No newline at end of file diff --git a/circuits/test/common/index.ts b/circuits/test/common/index.ts index 574e6e5..1d4c661 100644 --- a/circuits/test/common/index.ts +++ b/circuits/test/common/index.ts @@ -57,7 +57,7 @@ export function readJSONInputFile(filename: string, key: any[]): [number[], numb return [input, keyUnicode, output]; } -function toByte(data: string): number[] { +export function toByte(data: string): number[] { const byteArray = []; for (let i = 0; i < data.length; i++) { byteArray.push(data.charCodeAt(i)); diff --git a/circuits/test/http/extractor.test.ts b/circuits/test/http/extractor.test.ts index 6c94490..cdcfbdc 100644 --- a/circuits/test/http/extractor.test.ts +++ b/circuits/test/http/extractor.test.ts @@ -1,6 +1,6 @@ -import { circomkit, WitnessTester, generateDescription, readHTTPInputFile } from "../common"; +import { circomkit, WitnessTester, generateDescription, readHTTPInputFile, toByte } from "../common"; -describe("HTTP :: Extractor", async () => { +describe("HTTP :: body Extractor", async () => { let circuit: WitnessTester<["data"], ["response"]>; @@ -50,4 +50,39 @@ describe("HTTP :: Extractor", async () => { output3.pop(); generatePassCase(parsedHttp.input, output3, "output length less than actual length"); }); +}); + +describe("HTTP :: header Extractor", async () => { + let circuit: WitnessTester<["data", "header"], ["value"]>; + + function generatePassCase(input: number[], headerName: number[], headerValue: number[], desc: string) { + const description = generateDescription(input); + + it(`(valid) witness: ${description} ${desc}`, async () => { + circuit = await circomkit.WitnessTester(`ExtractHeaderValue`, { + file: "circuits/http/extractor", + template: "ExtractHeaderValue", + params: [input.length, headerName.length, headerValue.length], + }); + console.log("#constraints:", await circuit.getConstraintCount()); + + await circuit.expectPass({ data: input, header: headerName }, { value: headerValue }); + }); + } + + describe("response", async () => { + + let parsedHttp = readHTTPInputFile("get_response.http"); + + generatePassCase(parsedHttp.input, toByte("Content-Length"), toByte(parsedHttp.headers["Content-Length"]), ""); + + // let output2 = parsedHttp.bodyBytes.slice(0); + // output2.push(0, 0, 0, 0); + // generatePassCase(parsedHttp.input, output2, "output length more than actual length"); + + // let output3 = parsedHttp.bodyBytes.slice(0); + // output3.pop(); + // // output3.pop(); // TODO: fails due to shift subarray bug + // generatePassCase(parsedHttp.input, output3, "output length less than actual length"); + }); }); \ No newline at end of file