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: http + json extended example #91

Merged
merged 21 commits into from
Sep 24, 2024
Merged
Show file tree
Hide file tree
Changes from 6 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/interpreter.circom
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ template NextKVPairAtDepth(n, depth) {
signal input currByte;
signal output out;

var logMaxDepth = log2Ceil(n);
var logMaxDepth = log2Ceil(n+1);

component topOfStack = GetTopOfStack(n);
topOfStack.stack <== stack;
Expand All @@ -219,7 +219,7 @@ template NextKVPairAtDepth(n, depth) {
component syntax = Syntax();
signal isComma <== IsEqual()([currByte, syntax.COMMA]);
// pointer <= depth
signal atLessDepth <== LessEqThan(logMaxDepth)([pointer, depth]);
signal atLessDepth <== LessEqThan(logMaxDepth)([pointer-1, depth]);
// current depth is less than key depth
signal isCommaAtDepthLessThanCurrent <== isComma * atLessDepth;

Expand Down
88 changes: 88 additions & 0 deletions circuits/test/common/http.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { toByte } from ".";
import { join } from "path";
import { readFileSync } from "fs";

export function readLockFile<T>(filename: string): T {
const filePath = join(__dirname, "..", "..", "..", "examples", "http", "lockfile", filename);
const jsonString = readFileSync(filePath, 'utf-8');
const jsonData = JSON.parse(jsonString);
return jsonData;
}

export function getHeaders(data: Request | Response): [string, string][] {
const headers: [string, string][] = [];
let i = 1;
while (true) {
const nameKey = `headerName${i}`;
const valueKey = `headerValue${i}`;
if (nameKey in data && valueKey in data) {
headers.push([data[nameKey], data[valueKey]]);
i++;
} else {
break;
}
}
return headers;
}

export interface Request {
method: string,
target: string,
version: string,
[key: string]: string,
}

export interface Response {
version: string,
status: string,
message: string,
[key: string]: string,
}

export function readHTTPInputFile(filename: string) {
const filePath = join(__dirname, "..", "..", "..", "examples", "http", filename);
let data = readFileSync(filePath, 'utf-8');

let input = toByte(data);

// Split headers and body, accounting for possible lack of body
const parts = data.split('\r\n\r\n');
const headerSection = parts[0];
const bodySection = parts.length > 1 ? parts[1] : '';

// Function to parse headers into a dictionary
function parseHeaders(headerLines: string[]) {
const headers: { [id: string]: string } = {};

headerLines.forEach(line => {
const [key, value] = line.split(/:\s(.+)/);
if (key) headers[key] = value ? value : '';
});

return headers;
}

// Parse the headers
const headerLines = headerSection.split('\r\n');
const initialLine = headerLines[0].split(' ');
const headers = parseHeaders(headerLines.slice(1));

// Parse the body, if JSON response
let responseBody = {};
if (headers["Content-Type"] == "application/json" && bodySection) {
try {
responseBody = JSON.parse(bodySection);
} catch (e) {
console.error("Failed to parse JSON body:", e);
}
}

// Combine headers and body into an object
return {
input: input,
initialLine: initialLine,
headers: headers,
body: responseBody,
bodyBytes: toByte(bodySection || ''),
};
}
48 changes: 0 additions & 48 deletions circuits/test/common/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,52 +63,4 @@ export function toByte(data: string): number[] {
byteArray.push(data.charCodeAt(i));
}
return byteArray
}

export function readHTTPInputFile(filename: string) {
const filePath = join(__dirname, "..", "..", "..", "examples", "http", filename);
let data = readFileSync(filePath, 'utf-8');

let input = toByte(data);

// Split headers and body, accounting for possible lack of body
const parts = data.split('\r\n\r\n');
const headerSection = parts[0];
const bodySection = parts.length > 1 ? parts[1] : '';

// Function to parse headers into a dictionary
function parseHeaders(headerLines: string[]) {
const headers: { [id: string]: string } = {};

headerLines.forEach(line => {
const [key, value] = line.split(/:\s(.+)/);
if (key) headers[key] = value ? value : '';
});

return headers;
}

// Parse the headers
const headerLines = headerSection.split('\r\n');
const initialLine = headerLines[0].split(' ');
const headers = parseHeaders(headerLines.slice(1));

// Parse the body, if JSON response
let responseBody = {};
if (headers["Content-Type"] == "application/json" && bodySection) {
try {
responseBody = JSON.parse(bodySection);
} catch (e) {
console.error("Failed to parse JSON body:", e);
}
}

// Combine headers and body into an object
return {
input: input,
initialLine: initialLine,
headers: headers,
body: responseBody,
bodyBytes: toByte(bodySection || ''),
};
}
43 changes: 3 additions & 40 deletions circuits/test/http/codegen.test.ts
Original file line number Diff line number Diff line change
@@ -1,47 +1,10 @@
import { circomkit, WitnessTester, readHTTPInputFile, toByte } from "../common";
import { circomkit, WitnessTester, toByte } from "../common";
import { readHTTPInputFile, readLockFile, getHeaders, Request, Response } from "../common/http";
import { join } from "path";
import { spawn } from "child_process";
import { readFileSync } from "fs";

function readLockFile<T>(filename: string): T {
const filePath = join(__dirname, "..", "..", "..", "examples", "http", "lockfile", filename);
const jsonString = readFileSync(filePath, 'utf-8');
const jsonData = JSON.parse(jsonString);
return jsonData;
}

function getHeaders(data: Request | Response): [string, string][] {
const headers: [string, string][] = [];
let i = 1;
while (true) {
const nameKey = `headerName${i}`;
const valueKey = `headerValue${i}`;
if (nameKey in data && valueKey in data) {
headers.push([data[nameKey], data[valueKey]]);
i++;
} else {
break;
}
}
return headers;
}

interface Request {
method: string,
target: string,
version: string,
[key: string]: string,
}

interface Response {
version: string,
status: string,
message: string,
[key: string]: string,
}


function executeCodegen(circuitName: string, inputFileName: string, lockfileName: string) {
export function executeCodegen(circuitName: string, inputFileName: string, lockfileName: string) {
return new Promise((resolve, reject) => {
const inputFilePath = join(__dirname, "..", "..", "..", "examples", "http", inputFileName);
const lockfilePath = join(__dirname, "..", "..", "..", "examples", "http", "lockfile", lockfileName);
Expand Down
3 changes: 2 additions & 1 deletion circuits/test/http/extractor.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { circomkit, WitnessTester, generateDescription, readHTTPInputFile, toByte } from "../common";
import { circomkit, WitnessTester, generateDescription, toByte } from "../common";
import { readHTTPInputFile } from "../common/http";

describe("HTTP :: body Extractor", async () => {
let circuit: WitnessTester<["data"], ["response"]>;
Expand Down
3 changes: 2 additions & 1 deletion circuits/test/http/interpreter.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { circomkit, WitnessTester, generateDescription, toByte, readHTTPInputFile } from "../common";
import { circomkit, WitnessTester, generateDescription, toByte } from "../common";
import { readHTTPInputFile } from "../common/http";

describe("HTTP :: Interpreter", async () => {
describe("MethodMatch", async () => {
Expand Down
3 changes: 2 additions & 1 deletion circuits/test/http/locker.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { circomkit, WitnessTester, generateDescription, toByte, readHTTPInputFile } from "../common";
import { circomkit, WitnessTester, generateDescription, toByte } from "../common";
import { readHTTPInputFile } from "../common/http";

describe("HTTP :: Locker :: Request Line", async () => {
let circuit: WitnessTester<["data", "beginning", "middle", "final"], []>;
Expand Down
2 changes: 1 addition & 1 deletion circuits/test/json/extractor/extractor.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { join } from "path";
import { spawn } from "child_process";


function executeCodegen(circuitName: string, inputFileName: string, lockfileName: string) {
export function executeCodegen(circuitName: string, inputFileName: string, lockfileName: string) {
return new Promise((resolve, reject) => {
const inputFilePath = join(__dirname, "..", "..", "..", "..", "examples", "json", "test", inputFileName);
const lockfilePath = join(__dirname, "..", "..", "..", "..", "examples", "json", "lockfile", lockfileName);
Expand Down
83 changes: 83 additions & 0 deletions circuits/test/spotify_top_artists.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { circomkit, WitnessTester, toByte, readJSONInputFile } from "./common";
import { readLockFile, readHTTPInputFile, getHeaders as getHttpHeaders, Response } from "./common/http";
import { executeCodegen as httpLockfileCodegen } from "./http/codegen.test";
import { executeCodegen as jsonLockfileCodegen } from "./json/extractor/extractor.test";

describe("spotify top artists", async () => {
let http_circuit: WitnessTester<["data", "version", "status", "message", "header1", "value1", "header2", "value2"], ["body"]>;
let json_circuit: WitnessTester<["data", "key1", "key2", "key3", "key4", "key5", "key7", "key8", "key9"], ["value"]>;

it("POST response body extraction", async () => {
let httpLockfile = "spotify.lock"
let httpInputFile = "spotify_top_artists_response.http";
let httpCircuitName = "spotify_top_artists";

await httpLockfileCodegen(httpCircuitName, httpInputFile, `${httpLockfile}.json`);

let jsonFilename = "spotify";

await jsonLockfileCodegen(`${jsonFilename}_test`, `${jsonFilename}.json`, `${jsonFilename}.json`);

let index_0 = 0;

let [inputJson, key, output] = readJSONInputFile(
`${jsonFilename}.json`,
[
"data",
"me",
"profile",
"topArtists",
"items",
index_0,
"data",
"profile",
"name"
]
);

// json_circuit = await circomkit.WitnessTester(`Extract`, {
// file: `main/json_${jsonFilename}_test`,
// template: "ExtractStringValue",
// params: [input.length, 9, 4, 0, 2, 1, 7, 2, 10, 3, 5, 4, index_0, 5, 4, 6, 7, 7, 4, 8, 12],
// });
// console.log("#constraints:", await json_circuit.getConstraintCount());

// await json_circuit.expectPass({ data: input, key1: key[0], key2: key[1], key3: key[2], key4: key[3], key5: key[4], key7: key[6], key8: key[7], key9: key[8] }, { value: output });

const lockData = readLockFile<Response>(`${httpLockfile}.json`);
console.log("lockData: ", JSON.stringify(lockData));

const http = readHTTPInputFile(`${httpInputFile}`);
const inputHttp = http.input;

const headers = getHttpHeaders(lockData);

const params = [inputHttp.length, http.bodyBytes.length, lockData.version.length, lockData.status.length, lockData.message.length];
headers.forEach(header => {
params.push(header[0].length);
params.push(header[1].length);
});

// http_circuit = await circomkit.WitnessTester(`Extract`, {
// file: `main/http_${httpCircuitName}`,
// template: "LockHTTPResponse",
// params: params,
// });
// console.log("#constraints:", await http_circuit.getConstraintCount());

// match circuit output to original JSON value
const circuitInput: any = {
data: inputHttp,
version: toByte(lockData.version),
status: toByte(lockData.status),
message: toByte(lockData.message),
};

headers.forEach((header, index) => {
circuitInput[`header${index + 1}`] = toByte(header[0]);
circuitInput[`value${index + 1}`] = toByte(header[1]);
});

// await http_circuit.expectPass(circuitInput, { body: http.bodyBytes });
});
})
9 changes: 9 additions & 0 deletions examples/http/lockfile/spotify.lock.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"version": "HTTP/1.1",
"status": "200",
"message": "OK",
"headerName1": "content-type",
"headerValue1": "application/json; charset=utf-8",
"headerName2": "content-encoding",
"headerValue2": "gzip"

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is interesting. If the content is gzip vs identity, this likely fails.

Copy link
Collaborator Author

@lonerapier lonerapier Sep 21, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we can use Accept-Encoding: identity in request and perform LockHeader proof on request, right?

https://httpwg.org/specs/rfc9110.html#field.accept-encoding

}
3 changes: 3 additions & 0 deletions examples/http/spotify_top_artists_request.http
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
GET /v1/me/top/artists?time_range=medium_term&limit=1 HTTP/1.1
Host: api.spotify.com
Authorization: Bearer BQBXRpIm2NL08akEiaxB5l42eiq6Zd9Q0S2-0Q4k0CMoa5u8o_ah_Ddjxt6Mv3226AEDyKYcFPpgw_6Asg-Y2hJpcuMya8wzqyqgiV-KH0vcEq7EFzODXoaBxsB0wryVCWDF6p5dqcpIHOz4QJqQa9mUA6sFzYNyECglT-BGcRe_N9f_3aqYTGQ-kkE-devPkPkEfDcbziT6mOzJfGRzLw

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to commit this token...?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It expires every hour, at this point even I can't use it lol. but, yeah, will add this to gh secrets.

15 changes: 15 additions & 0 deletions examples/http/spotify_top_artists_response.http
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
HTTP/1.1 200 OK
content-type: application/json; charset=utf-8
content-encoding: gzip
Transfer-Encoding: chunked

{"data": {"me": {"profile": {"topArtists": {"__typename": "ArtistPageV2","items": [{"data": {"__typename": "Artist","profile": {"name": "Taylor Swift"},"uri": "spotify:artist:06HL4z0CvFAxyc27GXpf02"
}
}
],
"totalCount": 1
}
}
}
}
}
14 changes: 14 additions & 0 deletions examples/json/lockfile/spotify.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"keys": [
"data",
"me",
"profile",
"topArtists",
"items",
0,
"data",
"profile",
"name"
],
"value_type": "string"
}
10 changes: 10 additions & 0 deletions examples/json/test/spotify.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{"data": {"me": {"profile": {"topArtists": {"__typename": "ArtistPageV2","items": [{"data": {"__typename": "Artist","profile": {"name": "Taylor Swift"},"uri": "spotify:artist:06HL4z0CvFAxyc27GXpf02"
}
}
],
"totalCount": 1
}
}
}
}
}
Loading
Loading