diff --git a/Cargo.lock b/Cargo.lock
index 06d63c3..4433a59 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2,6 +2,15 @@
# It is not intended for manual editing.
version = 3
+[[package]]
+name = "aho-corasick"
+version = "1.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
+dependencies = [
+ "memchr",
+]
+
[[package]]
name = "anstream"
version = "0.6.15"
@@ -126,6 +135,7 @@ name = "pabuild"
version = "0.0.0"
dependencies = [
"clap",
+ "regex",
"serde",
"serde_json",
]
@@ -148,6 +158,35 @@ dependencies = [
"proc-macro2",
]
+[[package]]
+name = "regex"
+version = "1.10.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-automata",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-automata"
+version = "0.4.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.8.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b"
+
[[package]]
name = "ryu"
version = "1.0.18"
diff --git a/Cargo.toml b/Cargo.toml
index d252b64..3d0f952 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -6,3 +6,4 @@ edition = "2021"
serde = { version = "1.0.209", features = ["derive"] }
serde_json = "1.0.127"
clap = { version = "4.5.16", features = ["derive"] }
+regex = "1.10.6"
diff --git a/README.md b/README.md
index f9265aa..3314937 100644
--- a/README.md
+++ b/README.md
@@ -3,13 +3,13 @@
@@ -120,10 +120,10 @@ From the root of this repository, run:
```sh
cargo install --path .
```
-to install the `wpbuild` binary.
+to install the `pabuild` binary.
You can see a help menu with the subcommands by:
```sh
-wpbuild --help
+pabuild --help
```
This is our local Rust command line application.
Please see the [documentation](docs/pabuild.md) for how to use this alongside the other tools.
diff --git a/circuits/http/extractor.circom b/circuits/http/extractor.circom
index 03ad0c2..c2b0925 100644
--- a/circuits/http/extractor.circom
+++ b/circuits/http/extractor.circom
@@ -78,7 +78,7 @@ template ExtractResponse(DATA_BYTES, maxContentLength) {
valueStartingIndex[i] <== valueStartingIndex[i-1] + i * (1-isZeroMask[i]) * isPrevStartingIndex[i];
}
- response <== SelectSubArray(DATA_BYTES, maxContentLength)(dataMask, valueStartingIndex[DATA_BYTES-1]+1, DATA_BYTES - valueStartingIndex[DATA_BYTES-1]);
+ response <== SelectSubArray(DATA_BYTES, maxContentLength)(dataMask, valueStartingIndex[DATA_BYTES-1]+1, maxContentLength);
}
template ExtractHeaderValue(DATA_BYTES, headerNameLength, maxValueLength) {
diff --git a/circuits/test/http/codegen.test.ts b/circuits/test/http/codegen.test.ts
index d46ec48..aa209c6 100644
--- a/circuits/test/http/codegen.test.ts
+++ b/circuits/test/http/codegen.test.ts
@@ -41,11 +41,12 @@ interface Response {
}
-function executeCodegen(inputFilename: string, outputFilename: string) {
+function executeCodegen(circuitName: string, inputFileName: string, lockfileName: string) {
return new Promise((resolve, reject) => {
- const inputPath = join(__dirname, "..", "..", "..", "examples", "http", "lockfile", inputFilename);
+ const inputFilePath = join(__dirname, "..", "..", "..", "examples", "http", inputFileName);
+ const lockfilePath = join(__dirname, "..", "..", "..", "examples", "http", "lockfile", lockfileName);
- const codegen = spawn("cargo", ["run", "http", "--lockfile", inputPath, "--output-filename", outputFilename]);
+ const codegen = spawn("cargo", ["run", "codegen", "http", "--circuit-name", circuitName, "--input-file", inputFilePath, "--lockfile", lockfilePath]);
codegen.stdout.on('data', (data) => {
console.log(`stdout: ${data}`);
@@ -71,9 +72,10 @@ describe("HTTP :: Codegen :: Request", async () => {
it("(valid) GET:", async () => {
let lockfile = "request.lock";
let inputfile = "get_request.http";
+ let circuitName = "get_request_test";
// generate extractor circuit using codegen
- await executeCodegen(`${lockfile}.json`, lockfile);
+ await executeCodegen(circuitName, inputfile, `${lockfile}.json`);
const lockData = readLockFile(`${lockfile}.json`);
console.log("lockData: ", JSON.stringify(lockData));
@@ -89,7 +91,7 @@ describe("HTTP :: Codegen :: Request", async () => {
circuit = await circomkit.WitnessTester(`Extract`, {
- file: `main/${lockfile}`,
+ file: `main/http_${circuitName}`,
template: "LockHTTPRequest",
params: params,
});
@@ -113,9 +115,10 @@ describe("HTTP :: Codegen :: Request", async () => {
it("(invalid) GET:", async () => {
let lockfile = "request.lock";
let inputfile = "get_request.http";
+ let circuitName = "get_request_test";
// generate extractor circuit using codegen
- await executeCodegen(`${lockfile}.json`, lockfile);
+ await executeCodegen(circuitName, inputfile, `${lockfile}.json`);
const lockData = readLockFile(`${lockfile}.json`);
@@ -130,7 +133,7 @@ describe("HTTP :: Codegen :: Request", async () => {
circuit = await circomkit.WitnessTester(`Extract`, {
- file: `main/${lockfile}`,
+ file: `main/http_${circuitName}`,
template: "LockHTTPRequest",
params: params,
});
@@ -159,9 +162,10 @@ describe("HTTP :: Codegen :: Response", async () => {
it("(valid) GET:", async () => {
let lockfile = "response.lock";
let inputfile = "get_response.http";
+ let circuitName = "get_response_test";
// generate extractor circuit using codegen
- await executeCodegen(`${lockfile}.json`, lockfile);
+ await executeCodegen(circuitName, inputfile, `${lockfile}.json`);
const lockData = readLockFile(`${lockfile}.json`);
console.log("lockData: ", JSON.stringify(lockData));
@@ -179,7 +183,7 @@ describe("HTTP :: Codegen :: Response", async () => {
circuit = await circomkit.WitnessTester(`Extract`, {
- file: `main/${lockfile}`,
+ file: `main/http_${circuitName}`,
template: "LockHTTPResponse",
params: params,
});
@@ -205,9 +209,10 @@ describe("HTTP :: Codegen :: Response", async () => {
it("(invalid) GET:", async () => {
let lockfile = "response.lock";
let inputfile = "get_response.http";
+ let circuitName = "get_response_test";
// generate extractor circuit using codegen
- await executeCodegen(`${lockfile}.json`, lockfile);
+ await executeCodegen(circuitName, inputfile, `${lockfile}.json`);
const lockData = readLockFile(`${lockfile}.json`);
@@ -224,7 +229,7 @@ describe("HTTP :: Codegen :: Response", async () => {
circuit = await circomkit.WitnessTester(`Extract`, {
- file: `main/${lockfile}`,
+ file: `main/http_${circuitName}`,
template: "LockHTTPResponse",
params: params,
});
diff --git a/circuits/test/http/extractor.test.ts b/circuits/test/http/extractor.test.ts
index c69c7a7..6dbc0a4 100644
--- a/circuits/test/http/extractor.test.ts
+++ b/circuits/test/http/extractor.test.ts
@@ -31,7 +31,7 @@ describe("HTTP :: body Extractor", async () => {
let output3 = parsedHttp.bodyBytes.slice(0);
output3.pop();
- // output3.pop(); // TODO: fails due to shift subarray bug
+ output3.pop();
generatePassCase(parsedHttp.input, output3, "output length less than actual length");
});
@@ -75,15 +75,6 @@ describe("HTTP :: header Extractor", 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");
});
});
diff --git a/circuits/test/json/extractor/extractor.test.ts b/circuits/test/json/extractor/extractor.test.ts
index 9b9b6b3..c67a70b 100644
--- a/circuits/test/json/extractor/extractor.test.ts
+++ b/circuits/test/json/extractor/extractor.test.ts
@@ -3,11 +3,12 @@ import { join } from "path";
import { spawn } from "child_process";
-function executeCodegen(inputFilename: string, outputFilename: string) {
+function executeCodegen(circuitName: string, inputFileName: string, lockfileName: string) {
return new Promise((resolve, reject) => {
- const inputPath = join(__dirname, "..", "..", "..", "..", "examples", "json", "lockfile", inputFilename);
+ const inputFilePath = join(__dirname, "..", "..", "..", "..", "examples", "json", "test", inputFileName);
+ const lockfilePath = join(__dirname, "..", "..", "..", "..", "examples", "json", "lockfile", lockfileName);
- const codegen = spawn("cargo", ["run", "json", "--template", inputPath, "--output-filename", outputFilename]);
+ const codegen = spawn("cargo", ["run", "codegen", "json", "--circuit-name", circuitName, "--input-file", inputFilePath, "--lockfile", lockfilePath]);
codegen.stdout.on('data', (data) => {
console.log(`stdout: ${data}`);
@@ -34,13 +35,13 @@ describe("ExtractValue", async () => {
let filename = "value_string";
// generate extractor circuit using codegen
- await executeCodegen(`${filename}.json`, filename);
+ await executeCodegen(`${filename}_test`, `${filename}.json`, `${filename}.json`);
// read JSON input file into bytes
let [input, keyUnicode, output] = readJSONInputFile(`${filename}.json`, ["k"]);
circuit = await circomkit.WitnessTester(`Extract`, {
- file: `main/${filename}`,
+ file: `main/json_${filename}_test`,
template: "ExtractStringValue",
params: [input.length, 1, 1, 0, 1],
});
@@ -56,11 +57,11 @@ describe("ExtractValue", async () => {
it("two_keys: {\"key1\": \"abc\", \"key2\": \"def\" }", async () => {
let filename = "two_keys"
- await executeCodegen(`${filename}.json`, filename);
+ await executeCodegen(`${filename}_test`, `${filename}.json`, `${filename}.json`);
let [input, keyUnicode, output] = readJSONInputFile(`${filename}.json`, ["key2"]);
circuit = await circomkit.WitnessTester(`Extract`, {
- file: `main/${filename}`,
+ file: `main/json_${filename}_test`,
template: "ExtractStringValue",
params: [input.length, 1, 4, 0, 3],
});
@@ -71,11 +72,11 @@ describe("ExtractValue", async () => {
it("value_number: {\"k\": 69 }", async () => {
let filename = "value_number";
- await executeCodegen(`${filename}.json`, filename);
+ await executeCodegen(`${filename}_test`, `${filename}.json`, `${filename}.json`);
let [input, keyUnicode, output] = readJSONInputFile(`${filename}.json`, ["k"]);
circuit = await circomkit.WitnessTester(`Extract`, {
- file: `main/${filename}`,
+ file: `main/json_${filename}_test`,
template: "ExtractNumValue",
params: [input.length, 1, 1, 0, 2],
});
@@ -88,13 +89,14 @@ describe("ExtractValue", async () => {
it("value_array_string: { \"k\" : [ 420 , 69 , 4200 , 600 ], \"b\": [ \"ab\" , \"ba\", \"ccc\", \"d\" ] }", async () => {
let filename = "value_array_string";
- await executeCodegen(`${filename}.json`, filename);
+ let inputFileName = "value_array.json";
+ await executeCodegen(`${filename}_test`, inputFileName, `${filename}.json`);
for (let i = 0; i < 4; i++) {
- let [input, keyUnicode, output] = readJSONInputFile("value_array.json", ["b", i]);
+ let [input, keyUnicode, output] = readJSONInputFile(inputFileName, ["b", i]);
circuit = await circomkit.WitnessTester(`Extract`, {
- file: `main/${filename}`,
+ file: `main/json_${filename}_test`,
template: "ExtractStringValue",
params: [input.length, 2, 1, 0, i, 1, output.length],
});
@@ -106,13 +108,15 @@ describe("ExtractValue", async () => {
it("value_array_number: { \"k\" : [ 420 , 69 , 4200 , 600 ], \"b\": [ \"ab\" , \"ba\", \"ccc\", \"d\" ] }", async () => {
let filename = "value_array_number";
- await executeCodegen(`${filename}.json`, filename);
+ let inputFileName = "value_array.json";
+
+ await executeCodegen(`${filename}_test`, inputFileName, `${filename}.json`);
for (let i = 0; i < 4; i++) {
- let [input, keyUnicode, output] = readJSONInputFile("value_array.json", ["k", i]);
+ let [input, keyUnicode, output] = readJSONInputFile(inputFileName, ["k", i]);
circuit = await circomkit.WitnessTester(`Extract`, {
- file: `main/${filename}`,
+ file: `main/json_${filename}_test`,
template: "ExtractNumValue",
params: [input.length, 2, 1, 0, i, 1, output.length],
});
@@ -125,13 +129,13 @@ describe("ExtractValue", async () => {
it("value_array_nested: { \"a\": [[1,0],[0,1,3]] }", async () => {
let filename = "value_array_nested";
- await executeCodegen(`${filename}.json`, filename);
+ await executeCodegen(`${filename}_test`, `${filename}.json`, `${filename}.json`);
let index_0 = 1;
let index_1 = 0;
let [input, keyUnicode, output] = readJSONInputFile(`${filename}.json`, ["a", index_0, index_1]);
circuit = await circomkit.WitnessTester(`Extract`, {
- file: `main/${filename}`,
+ file: `main/json_${filename}_test`,
template: "ExtractNumValue",
params: [input.length, 3, 1, 0, index_0, 1, index_1, 2, 1],
});
@@ -150,12 +154,12 @@ describe("ExtractValueMultiDepth", () => {
it("value_object: { \"a\": { \"d\" : \"e\", \"e\": \"c\" }, \"e\": { \"f\": \"a\", \"e\": \"2\" } }", async () => {
let filename = "value_object";
- await executeCodegen(`${filename}.json`, filename);
+ await executeCodegen(`${filename}_test`, `${filename}.json`, `${filename}.json`);
let [input, keyUnicode, output] = readJSONInputFile(`${filename}.json`, ["e", "e"]);
circuit = await circomkit.WitnessTester(`Extract`, {
- file: `main/${filename}`,
+ file: `main/json_${filename}_test`,
template: "ExtractStringValue",
params: [input.length, 3, 1, 0, 1, 1, 1],
});
@@ -176,14 +180,14 @@ describe("ExtractValueArrayObject", () => {
it("value_array_object: {\"a\":[{\"b\":[1,4]},{\"c\":\"b\"}]}", async () => {
let filename = "value_array_object";
- await executeCodegen(`${filename}.json`, filename);
+ await executeCodegen(`${filename}_test`, `${filename}.json`, `${filename}.json`);
let index_0 = 0;
let index_1 = 0;
let [input, keyUnicode, output] = readJSONInputFile(`${filename}.json`, ["a", index_0, "b", index_1]);
circuit = await circomkit.WitnessTester(`Extract`, {
- file: `main/${filename}`,
+ file: `main/json_${filename}_test`,
template: "ExtractNumValue",
params: [input.length, 4, 1, 0, index_0, 1, 1, 2, index_1, 3, 1],
});
diff --git a/docs/pabuild.md b/docs/pabuild.md
index 97191c2..6fbe722 100644
--- a/docs/pabuild.md
+++ b/docs/pabuild.md
@@ -17,14 +17,17 @@ To get the basic idea, run
```sh
pabuild witness --help
```
-It can process and generate JSON files to be used for these circuits.
+It can process and generate input JSON files to be used for parser/extractor circuits.
+
+> [!NOTE]
+> `circuit-name` need to be **same** for witness generator and codegen.
### Examples
**JSON Parsing:**
If we have a given JSON file we want to parse such as [`examples/json/test/example.json`](../examples/json/test/example.json) for the `json-parser` circuit (see [`circuits.json`](../circuits.json)), then we can:
```sh
-pabuild witness json --input-file examples/json/test/example.json --output-dir inputs/json-parser --output-filename input.json json
+pabuild witness parser json --input-file examples/json/test/example.json --circuit-name json-parser
```
Afterwards, you can run `npx circomkit compile json-parser` then `circomkit witness json-parser input`.
@@ -33,81 +36,146 @@ Afterwards, you can run `npx circomkit compile json-parser` then `circomkit witn
If we have a given HTTP request/response (as a file) we want to parse such as [`examples/http/get_request.http`](../examples/http/get_request.http) for the `http-parser` circuit (see `circuits.json`), then we can:
```sh
-pabuild witness http --input-file examples/json/get_request.http --output-dir inputs/http-parser --output-filename input.json http
+pabuild witness parser http --input-file examples/http/get_request.http --circuit-name http-parser
```
Afterwards, you can run `npx circomkit compile http-parser` then `circomkit witness http-parser input`.
-## Codegen
+**JSON Extractor:**
+To extract a value out of a JSON, we need a lockfile that contains keys and value type.
-### JSON Extraction
-JSON extractor circuit is generated using rust to handle arbitrary keys and array indices.
+```sh
+pabuild witness extractor json --input-file examples/json/test/value_string.json --lockfile examples/json/lockfile/value_string.json --circuit-name value_string
+```
+
+**HTTP Extractor:**
+To extract reponse from HTTP, a lockfile need to be given with start line (method, status, version) and headers to be matched. Example can be found in [examples/http/lockfile](../examples/http/lockfile/).
+
+```sh
+pabuild witness extractor http --input-file examples/http/get_response.http --lockfile examples/http/lockfile/response.lock.json --circuit-name get-response
+```
+
+## Codegen
+Extractor circuit is generated using rust to handle arbitrary keys and array indices.
Run:
```sh
-pabuild json --help
+pabuild codegen --help
```
to get options:
```
-Usage: pabuild json [OPTIONS] --template
+Usage: pabuild codegen [OPTIONS] --circuit-name --input-file --lockfile
+
+Arguments:
+ [possible values: json, http]
Options:
- -t, --template Path to the JSON file selective-disclosure template
- -o, --output-filename Output circuit file name [default: extractor]
- -d, --debug Optional circuit debug logs
- -h, --help Print help
+ --circuit-name Name of the circuit (to be used in circomkit config)
+ --input-file Path to the JSON/HTTP file
+ --lockfile Path to the lockfile
+ -d, --debug Optional circuit debug logs
+ -h, --help Print help
```
Takes 3 input arguments:
-- `template`: input json file. Examples are located in [extractor](../examples/extractor/).
- - Should contain only two keys:
- - `keys`: list of all the keys in the input json
- - `value_type`: Currently only two value types are supported: `String`,`Number`.
-- `output-filename`: circuit filename to save. Located in [circuits/main](../circuits/main/). If not given, defaults to `extractor.circom`.
-- `debug`: Optional debug logs for parser and extractor output.
+- `input-file`: input json/http file. Examples are located in [examples/json](../examples/json/test/).
+- `lockfile`: keys and value type for extraction. Should contain only two keys:
+ - `keys`: list of all the keys for the value to be extracted.
+ - `value_type`: Currently only two value types are supported: `String`,`Number`.
+- `circuit-name`: circuit filename to save. Located in [circuits/main](../circuits/main/). Prefixed with `json_`
+- `debug`: Optional circuit debug logs.
+
+### JSON Extraction
To test an end-to-end JSON extraction proof:
-- Run codegen to generate circuits. Replace `value_string` with input filename.
+- Run codegen to generate circuits. Replace `value_string` with `circuit-name`.
```sh
- pabuild json --template examples/json/extractor/value_string.json --output-filename value_string
- ```
-
-- Compile circom circuit using
- ```
- circom ./circuits/main/value_string.circom --r1cs --wasm
+ pabuild codegen json --circuit-name value_string --input-file examples/json/test/value_string.json --lockfile examples/json/lockfile/value_string.json -d
```
-- To use circomkit: add circuit config to [circuits.json](../circuits.json). and input file to [inputs](../inputs/)
+- codegen adds circuit config to [circuits.json](../circuits.json) for circomkit support. Compile circuits using `npx circomkit compile value_string`
- Generate witness:
- ```bash
- node build/json_extract_value_string/json_extract_value_string_js/generate_witness inputs/json_extract_value_string/value_string.json build/json_extract_value_string/witness/
+ ```sh
+ node build/value_string/value_string_js/generate_witness.js build/value_string/value_string_js/value_string.wasm inputs/value_string/inputs.json build/value_string/witness.wtns
```
or generate using circomkit:
```bash
- npx circomkit witness json_extract_value_string value_string
+ npx circomkit witness value_string inputs
```
-- create trusted setup:
+- create trusted setup, circomkit downloads the required trusted setup file. Download manually, if using `snarkjs`:
```bash
- npx circomkit setup json_extract_value_string
+ npx circomkit setup value_string
# OR
- snarkjs groth16 setup build/json_extract_value_string/json_extract_value_string.r1cs ptau/powersOfTau28_hez_final_14.ptau build/json_extract_value_string/groth16_pkey.zkey
+ snarkjs groth16 setup build/value_string/value_string.r1cs ptau/powersOfTau28_hez_final_14.ptau build/value_string/groth16_pkey.zkey
+
+ snarkjs zkey contribute build/value_string/groth16_pkey.zkey build/value_string/groth16_pkey_1.zkey --name="random" -v
+
+ snarkjs zkey beacon build/value_string/groth16_pkey_1.zkey build/value_string/groth16_pkey_final.zkey 0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f 10 -n="Final Beacon phase2"
+
+ snarkjs zkey verify build/value_string/value_string.r1cs ptau/powersOfTau28_hez_final_14.ptau build/value_string/groth16_pkey_final.zkey
+
+ snarkjs zkey export verificationkey build/value_string/groth16_pkey_final.zkey build/value_string/groth16_vkey.json
```
- create proof:
```bash
- npx circomkit prove json_extract_value_string value_string
+ npx circomkit prove value_string inputs
# OR
- snarkjs groth16 prove build/json_extract_value_string/groth16_pkey.zkey build/json_extract_value_string/value_string/witness.wtns build/json_extract_value_string/value_string/groth16_proof.json inputs/json_extract_value_string/value_string.json
+ snarkjs groth16 prove build/value_string/groth16_pkey_final.zkey build/value_string/witness.wtns build/value_string/groth16_proof.json inputs/value_string/inputs.json
```
- verify proof:
```bash
- npx circomkit verify json_extract_value_string value_string
+ npx circomkit verify value_string value_string
# OR
- snarkjs groth16 verify build/json_extract_value_string/groth16_vkey.json inputs/json_extract_value_string/value_string.json build/json_extract_value_string/value_string/groth16_proof.json
+ snarkjs groth16 verify build/value_string/groth16_vkey.json inputs/value_string/inputs.json build/value_string/groth16_proof.json
```
### HTTP Locking and Extraction
-TODO
\ No newline at end of file
+To test an end-to-end HTTP response extraction proof:
+- Run codegen to generate circuits. Replace `get-response` with `circuit-name`.
+ ```sh
+ pabuild codegen http --circuit-name get-response --input-file examples/http/get_response.http --lockfile examples/http/lockfile/response.lock.json -d
+ ```
+
+- codegen adds circuit config to [circuits.json](../circuits.json) for circomkit support. Compile circuits using `npx circomkit compile get-response`
+
+- Generate witness:
+ ```sh
+ node build/get-response/get-response_js/generate_witness.js build/get-response/get-response_js/get-response.wasm inputs/get-response/inputs.json build/get-response/witness.wtns
+ ```
+ or generate using circomkit:
+ ```bash
+ npx circomkit witness get-response inputs
+ ```
+
+- create trusted setup, circomkit downloads the required trusted setup file. Download manually, if using `snarkjs`:
+ ```bash
+ npx circomkit setup get-response
+ # OR
+ snarkjs groth16 setup build/get-response/get-response.r1cs ptau/powersOfTau28_hez_final_16.ptau build/get-response/groth16_pkey.zkey
+
+ snarkjs zkey contribute build/get-response/groth16_pkey.zkey build/get-response/groth16_pkey_1.zkey --name="random" -v
+
+ snarkjs zkey beacon build/get-response/groth16_pkey_1.zkey build/get-response/groth16_pkey_final.zkey 0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f 10 -n="Final Beacon phase2"
+
+ snarkjs zkey verify build/get-response/get-response.r1cs ptau/powersOfTau28_hez_final_16.ptau build/get-response/groth16_pkey_final.zkey
+
+ snarkjs zkey export verificationkey build/get-response/groth16_pkey_final.zkey build/get-response/groth16_vkey.json
+ ```
+
+- create proof:
+ ```bash
+ npx circomkit prove get-response inputs
+ # OR
+ snarkjs groth16 prove build/get-response/groth16_pkey_final.zkey build/get-response/witness.wtns build/get-response/groth16_proof.json inputs/get-response/inputs.json
+ ```
+
+- verify proof:
+ ```bash
+ npx circomkit verify value_string value_string
+ # OR
+ snarkjs groth16 verify build/get-response/groth16_vkey.json inputs/get-response/inputs.json build/get-response/groth16_proof.json
+ ```
diff --git a/examples/json/lockfile/value_string.json b/examples/json/lockfile/value_string.json
index b25e4b5..c94b050 100644
--- a/examples/json/lockfile/value_string.json
+++ b/examples/json/lockfile/value_string.json
@@ -1,6 +1,6 @@
{
"keys": [
- "a"
+ "k"
],
"value_type": "string"
}
\ No newline at end of file
diff --git a/src/circuit_config.rs b/src/circuit_config.rs
new file mode 100644
index 0000000..7d43497
--- /dev/null
+++ b/src/circuit_config.rs
@@ -0,0 +1,50 @@
+use serde::{Deserialize, Serialize};
+use std::{collections::HashMap, env};
+
+/// circuit config used for circomkit support
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub struct CircomkitCircuitConfig {
+ /// file name containing the circuit template
+ pub file: String,
+ /// circuit template name
+ pub template: String,
+ /// circuit parameters
+ pub params: Vec,
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub struct CircomkitConfig(HashMap);
+
+/// Writes config to `circuits.json` for circomkit support
+/// # Inputs
+/// - `name`: circuit name
+/// - `circuit_config`: [`CircomkitCircuitConfig`]
+pub fn write_config(
+ name: String,
+ circuit_config: &CircomkitCircuitConfig,
+) -> Result<(), Box> {
+ let mut circomkit_config = env::current_dir()?;
+ circomkit_config.push("circuits.json");
+
+ let _ = std::fs::File::create_new(&circomkit_config);
+
+ let mut circomkit_circuits: CircomkitConfig =
+ serde_json::from_slice(&std::fs::read(&circomkit_config)?)?;
+
+ if let Some(circuits_inputs) = circomkit_circuits.0.get_mut(&name) {
+ *circuits_inputs = circuit_config.clone();
+ } else {
+ let _ = circomkit_circuits
+ .0
+ .insert(name.clone(), circuit_config.clone());
+ }
+
+ std::fs::write(
+ circomkit_config.clone(),
+ serde_json::to_string_pretty(&circomkit_circuits)?,
+ )?;
+
+ println!("Config updated: {}", circomkit_config.display());
+
+ Ok(())
+}
diff --git a/src/http.rs b/src/codegen/http.rs
similarity index 73%
rename from src/http.rs
rename to src/codegen/http.rs
index f0cbb65..217d023 100644
--- a/src/http.rs
+++ b/src/codegen/http.rs
@@ -1,6 +1,14 @@
-use super::*;
+use crate::{
+ circuit_config::{write_config, CircomkitCircuitConfig},
+ witness::read_input_file_as_bytes,
+ ExtractorArgs, FileType,
+};
+use regex::Regex;
+use serde::{Deserialize, Serialize};
+
use std::{
collections::HashMap,
+ error::Error,
fs::{self, create_dir_all},
};
@@ -11,7 +19,7 @@ pub enum HttpData {
Response(Response),
}
-#[derive(Debug, Serialize, Deserialize)]
+#[derive(Debug, Deserialize)]
pub struct Request {
method: String,
target: String,
@@ -21,7 +29,7 @@ pub struct Request {
headers: HashMap,
}
-#[derive(Debug, Serialize, Deserialize)]
+#[derive(Debug, Deserialize)]
pub struct Response {
version: String,
status: String,
@@ -49,6 +57,46 @@ impl std::fmt::Debug for HttpData {
}
}
+impl Serialize for Request {
+ fn serialize(&self, serializer: S) -> Result
+ where
+ S: serde::Serializer,
+ {
+ use serde::ser::SerializeMap;
+ let mut map = serializer.serialize_map(Some(3 + self.headers.len() * 2))?;
+
+ map.serialize_entry("method", self.method.as_bytes())?;
+ map.serialize_entry("target", self.target.as_bytes())?;
+ map.serialize_entry("version", self.version.as_bytes())?;
+
+ for (i, (key, value)) in self.headers.iter().enumerate() {
+ map.serialize_entry(&format!("header{}", i + 1), key.as_bytes())?;
+ map.serialize_entry(&format!("value{}", i + 1), value.as_bytes())?;
+ }
+ map.end()
+ }
+}
+
+impl Serialize for Response {
+ fn serialize(&self, serializer: S) -> Result
+ where
+ S: serde::Serializer,
+ {
+ use serde::ser::SerializeMap;
+ let mut map = serializer.serialize_map(Some(3 + self.headers.len() * 2))?;
+
+ map.serialize_entry("version", self.version.as_bytes())?;
+ map.serialize_entry("status", self.status.as_bytes())?;
+ map.serialize_entry("message", self.message.as_bytes())?;
+
+ for (i, (key, value)) in self.headers.iter().enumerate() {
+ map.serialize_entry(&format!("header{}", i + 1), key.as_bytes())?;
+ map.serialize_entry(&format!("value{}", i + 1), value.as_bytes())?;
+ }
+ map.end()
+ }
+}
+
fn deserialize_headers<'de, D>(deserializer: D) -> Result, D::Error>
where
D: serde::Deserializer<'de>,
@@ -71,8 +119,8 @@ where
const PRAGMA: &str = "pragma circom 2.1.9;\n\n";
fn build_http_circuit(
- data: HttpData,
- output_filename: String,
+ data: &HttpData,
+ output_filename: &String,
debug: bool,
) -> Result<(), Box> {
let mut circuit_buffer = String::new();
@@ -267,7 +315,7 @@ fn build_http_circuit(
methodIsEqual[data_idx] === 1;
}
- // Get the target bytes
+ // Get the target bytes
startLineMask[data_idx] <== inStartLine()(State[data_idx].parsing_start);
targetMask[data_idx] <== inStartMiddle()(State[data_idx].parsing_start);
versionMask[data_idx] <== inStartEnd()(State[data_idx].parsing_start);
@@ -286,7 +334,7 @@ fn build_http_circuit(
versionIsEqual[data_idx] === 1;
}
- // Get the status bytes
+ // Get the status bytes
startLineMask[data_idx] <== inStartLine()(State[data_idx].parsing_start);
statusMask[data_idx] <== inStartMiddle()(State[data_idx].parsing_start);
messageMask[data_idx] <== inStartEnd()(State[data_idx].parsing_start);
@@ -346,7 +394,7 @@ fn build_http_circuit(
// Get the output body bytes
{
if let HttpData::Response(_) = data {
- circuit_buffer += r#"
+ circuit_buffer += r#"
signal bodyStartingIndex[DATA_BYTES];
signal isZeroMask[DATA_BYTES];
signal isPrevStartingIndex[DATA_BYTES];
@@ -384,13 +432,13 @@ fn build_http_circuit(
signal targetMatch <== SubstringMatchWithIndex(DATA_BYTES, targetLen)(data, target, 100, target_start_counter);
targetMatch === 1;
targetLen === target_end_counter - target_start_counter - 1;
-
+
// Check version is correct by substring match and length check
// TODO: change r
signal versionMatch <== SubstringMatchWithIndex(DATA_BYTES, versionLen)(data, version, 100, target_end_counter);
versionMatch === 1;
// -2 here for the CRLF
- versionLen === version_end_counter - target_end_counter - 2;
+ versionLen === version_end_counter - target_end_counter - 2;
"#;
}
HttpData::Response(_) => {
@@ -403,13 +451,13 @@ fn build_http_circuit(
signal statusMatch <== SubstringMatchWithIndex(DATA_BYTES, statusLen)(data, status, 100, status_start_counter);
statusMatch === 1;
statusLen === status_end_counter - status_start_counter - 1;
-
+
// Check message is correct by substring match and length check
// TODO: change r
signal messageMatch <== SubstringMatchWithIndex(DATA_BYTES, messageLen)(data, message, 100, status_end_counter);
messageMatch === 1;
// -2 here for the CRLF
- messageLen === message_end_counter - status_end_counter - 2;
+ messageLen === message_end_counter - status_end_counter - 2;
"#;
}
}
@@ -441,13 +489,108 @@ fn build_http_circuit(
Ok(())
}
-// TODO: This needs to codegen a circuit now.
-pub fn http_circuit(args: HttpArgs) -> Result<(), Box> {
+fn parse_http_file(
+ locfile: &HttpData,
+ input: Vec,
+) -> Result<(HttpData, Vec), Box> {
+ let input_string = String::from_utf8(input)?;
+
+ let parts: Vec<&str> = input_string.split("\r\n\r\n").collect();
+ assert!(parts.len() <= 2);
+
+ let mut body = vec![];
+ if parts.len() == 2 {
+ body = parts[1].as_bytes().to_vec();
+ }
+
+ let headers: Vec<&str> = parts[0].split("\r\n").collect();
+ let start_line: Vec<&str> = headers[0].split(" ").collect();
+ assert_eq!(start_line.len(), 3);
+
+ let (_, headers) = headers.split_at(1);
+ let mut headers_map = HashMap::::new();
+ let re = Regex::new(r":\s(.+)").unwrap();
+ for &header in headers {
+ let key_value: Vec<&str> = re.split(header).collect();
+ assert_eq!(key_value.len(), 2);
+ headers_map.insert(key_value[0].to_string(), key_value[1].to_string());
+ }
+
+ let http_data = match locfile {
+ HttpData::Request(_) => HttpData::Request(Request {
+ method: start_line[0].to_string(),
+ target: start_line[1].to_string(),
+ version: start_line[2].to_string(),
+ headers: headers_map,
+ }),
+ HttpData::Response(_) => HttpData::Response(Response {
+ version: start_line[0].to_string(),
+ status: start_line[1].to_string(),
+ message: start_line[2].to_string(),
+ headers: headers_map,
+ }),
+ };
+
+ Ok((http_data, body))
+}
+
+fn build_circuit_config(
+ args: &ExtractorArgs,
+ lockfile: &HttpData,
+ codegen_filename: String,
+) -> Result> {
+ let input = read_input_file_as_bytes(&FileType::Http, args.input_file.clone())?;
+
+ let (_, http_body) = parse_http_file(lockfile, input.clone())?;
+
+ let circuit_template_name = match lockfile {
+ HttpData::Request(_) => String::from("LockHTTPRequest"),
+ HttpData::Response(_) => String::from("LockHTTPResponse"),
+ };
+
+ let mut params = vec![input.len()];
+
+ match lockfile {
+ HttpData::Request(request) => {
+ params.push(request.method.len());
+ params.push(request.target.len());
+ params.push(request.version.len());
+ for (key, value) in request.headers.iter() {
+ params.push(key.len());
+ params.push(value.len());
+ }
+ }
+ HttpData::Response(response) => {
+ params.push(http_body.len());
+ params.push(response.version.len());
+ params.push(response.status.len());
+ params.push(response.message.len());
+ for (key, value) in response.headers.iter() {
+ params.push(key.len());
+ params.push(value.len());
+ }
+ }
+ }
+
+ Ok(CircomkitCircuitConfig {
+ file: format!("main/{}", codegen_filename),
+ template: circuit_template_name,
+ params,
+ })
+}
+
+pub fn http_circuit(args: ExtractorArgs) -> Result<(), Box> {
let data = std::fs::read(&args.lockfile)?;
let http_data: HttpData = serde_json::from_slice(&data)?;
- build_http_circuit(http_data, args.output_filename, args.debug)?;
+ let codegen_filename = format!("http_{}", args.circuit_name);
+
+ build_http_circuit(&http_data, &codegen_filename, args.debug)?;
+
+ let circomkit_circuit_input = build_circuit_config(&args, &http_data, codegen_filename)?;
+
+ write_config(args.circuit_name, &circomkit_circuit_input)?;
Ok(())
}
diff --git a/src/json.rs b/src/codegen/json.rs
similarity index 80%
rename from src/json.rs
rename to src/codegen/json.rs
index 7a21e31..d1e6b78 100644
--- a/src/json.rs
+++ b/src/codegen/json.rs
@@ -1,13 +1,16 @@
+use serde::{Deserialize, Serialize};
+use serde_json::Value;
use std::{
+ cmp::max_by,
+ collections::HashMap,
fs::{self, create_dir_all},
str::FromStr,
};
-use std::collections::HashMap;
-
-use super::*;
-
-const PRAGMA: &str = "pragma circom 2.1.9;\n\n";
+use crate::{
+ circuit_config::{write_config, CircomkitCircuitConfig},
+ ExtractorArgs,
+};
#[derive(Debug, Deserialize)]
pub enum ValueType {
@@ -21,16 +24,16 @@ pub enum ValueType {
#[serde(untagged)]
pub enum Key {
String(String),
- Num(i64),
+ Num(usize),
}
#[derive(Debug, Deserialize)]
-pub struct JsonLockfile {
+pub struct Lockfile {
keys: Vec,
value_type: ValueType,
}
-impl JsonLockfile {
+impl Lockfile {
pub fn as_bytes(&self) -> HashMap> {
let mut keys = HashMap::>::new();
for (i, key) in self.keys.iter().enumerate() {
@@ -43,8 +46,16 @@ impl JsonLockfile {
}
}
-fn extract_string(data: JsonLockfile, circuit_buffer: &mut String, debug: bool) {
- *circuit_buffer += "template ExtractStringValue(DATA_BYTES, MAX_STACK_HEIGHT, ";
+fn extract_string(
+ config: &CircomkitCircuitConfig,
+ data: &Lockfile,
+ circuit_buffer: &mut String,
+ debug: bool,
+) {
+ *circuit_buffer += &format!(
+ "template {}(DATA_BYTES, MAX_STACK_HEIGHT, ",
+ config.template
+ );
for (i, key) in data.keys.iter().enumerate() {
match key {
Key::String(_) => *circuit_buffer += &format!("keyLen{}, depth{}, ", i + 1, i + 1),
@@ -108,8 +119,16 @@ fn extract_string(data: JsonLockfile, circuit_buffer: &mut String, debug: bool)
"#;
}
-fn extract_number(data: JsonLockfile, circuit_buffer: &mut String, debug: bool) {
- *circuit_buffer += "template ExtractNumValue(DATA_BYTES, MAX_STACK_HEIGHT, ";
+fn extract_number(
+ config: &CircomkitCircuitConfig,
+ data: &Lockfile,
+ circuit_buffer: &mut String,
+ debug: bool,
+) {
+ *circuit_buffer += &format!(
+ "template {}(DATA_BYTES, MAX_STACK_HEIGHT, ",
+ config.template
+ );
for (i, key) in data.keys.iter().enumerate() {
match key {
Key::String(_) => *circuit_buffer += &format!("keyLen{}, depth{}, ", i + 1, i + 1),
@@ -184,8 +203,9 @@ fn extract_number(data: JsonLockfile, circuit_buffer: &mut String, debug: bool)
}
fn build_json_circuit(
- data: JsonLockfile,
- output_filename: String,
+ config: &CircomkitCircuitConfig,
+ data: &Lockfile,
+ output_filename: &String,
debug: bool,
) -> Result<(), Box> {
let mut circuit_buffer = String::new();
@@ -195,7 +215,7 @@ fn build_json_circuit(
circuit_buffer += &format!("{:#?}", data);
circuit_buffer += "\n*/\n";
- circuit_buffer += PRAGMA;
+ circuit_buffer += "pragma circom 2.1.9;\n\n";
circuit_buffer += "include \"../json/interpreter.circom\";\n\n";
// template ExtractValue(DATA_BYTES, MAX_STACK_HEIGHT, keyLen1, depth1, index2, depth2, keyLen3, depth3, index4, depth4, maxValueLen) {
@@ -515,8 +535,8 @@ fn build_json_circuit(
}
match data.value_type {
- ValueType::String => extract_string(data, &mut circuit_buffer, debug),
- ValueType::Number => extract_number(data, &mut circuit_buffer, debug),
+ ValueType::String => extract_string(config, data, &mut circuit_buffer, debug),
+ ValueType::Number => extract_number(config, data, &mut circuit_buffer, debug),
}
// write circuits to file
@@ -536,11 +556,104 @@ fn build_json_circuit(
Ok(())
}
-pub fn json_circuit(args: JsonArgs) -> Result<(), Box> {
- let data = std::fs::read(&args.template)?;
- let json_data: JsonLockfile = serde_json::from_slice(&data)?;
+pub fn json_max_stack_height(input: &[u8]) -> usize {
+ let mut max_stack_height = 1;
+ let mut curr_stack_height = 1;
+ let mut inside_string: bool = false;
+
+ for (i, char) in input.iter().skip(1).enumerate() {
+ match char {
+ b'"' if input[i] != b'\\' => inside_string = !inside_string,
+ b'{' | b'[' if !inside_string => {
+ curr_stack_height += 1;
+ max_stack_height = max_by(max_stack_height, curr_stack_height, |x, y| x.cmp(y));
+ }
+ b'}' | b']' if !inside_string => curr_stack_height -= 1,
+ _ => {}
+ }
+ }
+
+ max_stack_height
+}
+
+/// Builds circuit config for circomkit support.
+pub fn build_circuit_config(
+ args: &ExtractorArgs,
+ lockfile: &Lockfile,
+ codegen_filename: &str,
+) -> Result> {
+ let input = fs::read(args.input_file.clone())?;
+
+ let circuit_template_name = match lockfile.value_type {
+ ValueType::String => String::from("ExtractStringValue"),
+ ValueType::Number => String::from("ExtractNumValue"),
+ };
+
+ // build circuit arguments
+ // [DATA_BYTES, MAX_STACK_HEIGHT, keyLen1, depth1, ..., maxValueLen]
+ let mut params = vec![input.len(), json_max_stack_height(&input)];
+
+ let mut current_value: Value = serde_json::from_slice(&input)?;
+ for (i, key) in lockfile.keys.iter().enumerate() {
+ match key {
+ Key::String(key) => {
+ if let Some(value) = current_value.get_mut(key) {
+ // update circuit params
+ params.push(key.len());
+
+ // update current object value inside key
+ current_value = value.to_owned();
+ } else {
+ return Err(String::from("provided key not present in input JSON").into());
+ }
+ }
+ Key::Num(index) => {
+ if let Some(value) = current_value.get_mut(index) {
+ params.push(index.to_string().as_bytes().len());
+ current_value = value.to_owned();
+ } else {
+ return Err(String::from("provided index not present in input JSON").into());
+ }
+ }
+ }
+ params.push(i);
+ }
+
+ // get value of specified key
+ // Currently only supports number, string
+ let value_bytes = match lockfile.value_type {
+ ValueType::Number => {
+ if !current_value.is_u64() {
+ return Err(String::from("value type doesn't match").into());
+ }
+ current_value.as_u64().unwrap().to_string()
+ }
+ ValueType::String => {
+ if !current_value.is_string() {
+ return Err(String::from("value type doesn't match").into());
+ }
+ current_value.as_str().unwrap().to_string()
+ }
+ };
+ params.push(value_bytes.as_bytes().len());
+
+ Ok(CircomkitCircuitConfig {
+ file: format!("main/{}", codegen_filename),
+ template: circuit_template_name,
+ params,
+ })
+}
+
+pub fn json_circuit(args: ExtractorArgs) -> Result<(), Box> {
+ let lockfile: Lockfile = serde_json::from_slice(&std::fs::read(&args.lockfile)?)?;
+
+ let circuit_filename = format!("json_{}", args.circuit_name);
+
+ let config = build_circuit_config(&args, &lockfile, &circuit_filename)?;
+
+ build_json_circuit(&config, &lockfile, &circuit_filename, args.debug)?;
- build_json_circuit(json_data, args.output_filename, args.debug)?;
+ write_config(args.circuit_name, &config)?;
Ok(())
}
diff --git a/src/codegen/mod.rs b/src/codegen/mod.rs
new file mode 100644
index 0000000..9550283
--- /dev/null
+++ b/src/codegen/mod.rs
@@ -0,0 +1,2 @@
+pub mod http;
+pub mod json;
\ No newline at end of file
diff --git a/src/main.rs b/src/main.rs
index 5a1757d..3678922 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,9 +1,8 @@
use clap::{Parser, Subcommand};
-use serde::{Deserialize, Serialize};
use std::{error::Error, path::PathBuf};
-pub mod http;
-pub mod json;
+pub mod circuit_config;
+pub mod codegen;
pub mod witness;
#[derive(Parser, Debug)]
@@ -15,83 +14,72 @@ pub struct Args {
#[derive(Subcommand, Debug)]
pub enum Command {
- ParserWitness(ParserWitnessArgs),
- ExtractorWitness(ExtractorWitnessArgs),
- Json(JsonArgs),
- Http(HttpArgs),
+ #[command(subcommand)]
+ Witness(WitnessType),
+ Codegen(ExtractorArgs),
+}
+
+#[derive(clap::ValueEnum, Clone, Debug, PartialEq)]
+pub enum FileType {
+ Json,
+ Http,
+}
+
+#[derive(Debug, Parser)]
+pub enum WitnessType {
+ Parser(ParserWitnessArgs),
+ Extractor(ExtractorWitnessArgs),
}
#[derive(Parser, Debug)]
pub struct ParserWitnessArgs {
- #[arg(global = true, value_enum)]
- subcommand: WitnessSubcommand,
+ #[arg(value_enum)]
+ subcommand: FileType,
/// Path to the JSON file
- #[arg(global = true, long)]
+ #[arg(long)]
input_file: PathBuf,
- /// Output directory (will be created if it doesn't exist)
- #[arg(global = true, long, default_value = ".")]
- output_dir: PathBuf,
-
- /// Output filename (will be created if it doesn't exist)
- #[arg(global = true, long, default_value = "output.json")]
- output_filename: String,
+ /// Name of the circuit (to be used in circomkit config)
+ #[arg(long)]
+ circuit_name: String,
}
#[derive(Parser, Debug)]
pub struct ExtractorWitnessArgs {
- #[arg(global = true, value_enum)]
- subcommand: WitnessSubcommand,
+ #[arg(value_enum)]
+ subcommand: FileType,
+
+ /// Name of the circuit (to be used in circomkit config)
+ #[arg(long)]
+ circuit_name: String,
/// Path to the JSON file
- #[arg(global = true, long)]
+ #[arg(long)]
input_file: PathBuf,
/// Path to the lockfile
- #[arg(global = true, long)]
+ #[arg(long)]
lockfile: PathBuf,
-
- /// Output directory (will be created if it doesn't exist)
- #[arg(global = true, long, default_value = ".")]
- output_dir: PathBuf,
-
- /// Output filename (will be created if it doesn't exist)
- #[arg(global = true, long, default_value = "output.json")]
- output_filename: String,
-}
-
-#[derive(clap::ValueEnum, Clone, Debug)]
-pub enum WitnessSubcommand {
- Json,
- Http,
}
#[derive(Parser, Debug)]
-pub struct JsonArgs {
- /// Path to the JSON file selective-disclosure template
- #[arg(long, short)]
- template: PathBuf,
+pub struct ExtractorArgs {
+ #[arg(value_enum)]
+ subcommand: FileType,
- /// Output circuit file name
- #[arg(long, short, default_value = "extractor")]
- output_filename: String,
+ /// Name of the circuit (to be used in circomkit config)
+ #[arg(long)]
+ circuit_name: String,
- /// Optional circuit debug logs
- #[arg(long, short, action = clap::ArgAction::SetTrue)]
- debug: bool,
-}
+ /// Path to the JSON/HTTP file
+ #[arg(long)]
+ input_file: PathBuf,
-#[derive(Parser, Debug)]
-pub struct HttpArgs {
- /// Path to the JSON file
+ /// Path to the lockfile
#[arg(long)]
lockfile: PathBuf,
- /// Output circuit file name
- #[arg(long, short, default_value = "extractor")]
- output_filename: String,
-
/// Optional circuit debug logs
#[arg(long, short, action = clap::ArgAction::SetTrue)]
debug: bool,
@@ -99,9 +87,13 @@ pub struct HttpArgs {
pub fn main() -> Result<(), Box> {
match Args::parse().command {
- Command::ParserWitness(args) => witness::parser_witness(args),
- Command::Json(args) => json::json_circuit(args),
- Command::Http(args) => http::http_circuit(args),
- Command::ExtractorWitness(args) => witness::extractor_witness(args),
+ Command::Witness(witness_type) => match witness_type {
+ WitnessType::Parser(args) => witness::parser_witness(args),
+ WitnessType::Extractor(args) => witness::extractor_witness(args),
+ },
+ Command::Codegen(args) => match args.subcommand {
+ FileType::Http => codegen::http::http_circuit(args),
+ FileType::Json => codegen::json::json_circuit(args),
+ },
}
}
diff --git a/src/witness.rs b/src/witness.rs
index 522e6c8..ec09bd9 100644
--- a/src/witness.rs
+++ b/src/witness.rs
@@ -1,19 +1,33 @@
-use json::JsonLockfile;
-
-use super::*;
-use std::{collections::HashMap, io::Write};
+use crate::{
+ codegen::{
+ http::HttpData,
+ json::{json_max_stack_height, Lockfile},
+ },
+ ExtractorWitnessArgs, FileType, ParserWitnessArgs,
+};
+use std::{collections::HashMap, io::Write, path::PathBuf};
#[derive(serde::Serialize)]
-pub struct Witness {
+pub struct ParserWitness {
data: Vec,
}
#[derive(serde::Serialize)]
-pub struct ExtractorWitness {
+pub struct JsonExtractorWitness {
data: Vec,
+
+ #[serde(flatten)]
keys: HashMap>,
}
+#[derive(serde::Serialize)]
+pub struct HttpExtractorWitness {
+ data: Vec,
+
+ #[serde(flatten)]
+ http_data: HttpData,
+}
+
fn print_boxed_output(lines: Vec) {
// Determine the maximum length of the lines
let max_length = lines.iter().map(|line| line.len()).max().unwrap_or(0);
@@ -30,11 +44,14 @@ fn print_boxed_output(lines: Vec) {
println!("{}", bottom_border);
}
-pub fn parser_witness(args: ParserWitnessArgs) -> Result<(), Box> {
- let data = match &args.subcommand {
- WitnessSubcommand::Json => std::fs::read(args.input_file)?,
- WitnessSubcommand::Http => {
- let mut data = std::fs::read(args.input_file)?;
+pub fn read_input_file_as_bytes(
+ file_type: &FileType,
+ file_path: PathBuf,
+) -> Result, Box> {
+ match file_type {
+ FileType::Json => Ok(std::fs::read(file_path)?),
+ FileType::Http => {
+ let mut data = std::fs::read(file_path)?;
let mut i = 0;
// convert LF to CRLF
while i < data.len() {
@@ -45,68 +62,108 @@ pub fn parser_witness(args: ParserWitnessArgs) -> Result<(), Box Result<(), Box> {
+ let data = read_input_file_as_bytes(&args.subcommand, args.input_file)?;
+
+ let witness = ParserWitness { data: data.clone() };
- if !args.output_dir.exists() {
- std::fs::create_dir_all(&args.output_dir)?;
+ let mut output_dir = std::env::current_dir()?;
+ output_dir.push("inputs");
+ output_dir.push(args.circuit_name);
+
+ if !output_dir.exists() {
+ std::fs::create_dir_all(&output_dir)?;
}
- let output_file = args.output_dir.join(args.output_filename);
+ let output_file = output_dir.join("inputs.json");
let mut file = std::fs::File::create(output_file)?;
+
file.write_all(serde_json::to_string_pretty(&witness)?.as_bytes())?;
// Prepare lines to print
let mut lines = Vec::new();
lines.push(format!("Data length: {}", data.len()));
+ if args.subcommand == FileType::Json {
+ lines.push(format!(
+ "Max stack height: {}",
+ json_max_stack_height(&data)
+ ))
+ }
// Print the output inside a nicely formatted box
print_boxed_output(lines);
Ok(())
}
-fn read_input_file_as_bytes(
- file_type: WitnessSubcommand,
- file_path: PathBuf,
-) -> Result, Box> {
- match file_type {
- WitnessSubcommand::Json => Ok(std::fs::read(file_path)?),
- WitnessSubcommand::Http => {
- let mut data = std::fs::read(file_path)?;
- let mut i = 0;
- // convert LF to CRLF
- while i < data.len() {
- if data[i] == 10 && (i == 0 || data[i - 1] != 13) {
- data.insert(i, 13);
- i += 2;
- } else {
- i += 1;
- }
- }
- Ok(data)
- }
+fn json_extractor_witness(args: ExtractorWitnessArgs) -> Result<(), Box> {
+ // read input and lockfile
+ let input_data = read_input_file_as_bytes(&args.subcommand, args.input_file)?;
+
+ let lockfile_data = std::fs::read(&args.lockfile)?;
+ let lockfile: Lockfile = serde_json::from_slice(&lockfile_data)?;
+
+ // create extractor witness data
+ let witness = JsonExtractorWitness {
+ data: input_data.clone(),
+ keys: lockfile.as_bytes(),
+ };
+
+ // create output dir if not exists
+ let mut output_dir = std::env::current_dir()?;
+ output_dir.push("inputs");
+ output_dir.push(&args.circuit_name);
+ if !output_dir.exists() {
+ std::fs::create_dir_all(&output_dir)?;
}
+
+ // write input file
+ let output_file = output_dir.join("inputs.json");
+ let mut file = std::fs::File::create(output_file)?;
+ file.write_all(serde_json::to_string_pretty(&witness)?.as_bytes())?;
+
+ // Prepare lines to print
+ let mut lines = Vec::new();
+ lines.push(format!("Data length: {}", input_data.len()));
+ lines.push(format!(
+ "Max stack height: {}",
+ json_max_stack_height(&input_data)
+ ));
+
+ // Print the output inside a nicely formatted box
+ print_boxed_output(lines);
+
+ Ok(())
}
-pub fn extractor_witness(args: ExtractorWitnessArgs) -> Result<(), Box> {
- let input_data = read_input_file_as_bytes(args.subcommand, args.input_file)?;
+
+fn http_extractor_witness(args: ExtractorWitnessArgs) -> Result<(), Box> {
+ // read input and lockfile
+ let input_data = read_input_file_as_bytes(&args.subcommand, args.input_file)?;
let lockfile_data = std::fs::read(&args.lockfile)?;
- let lockfile: JsonLockfile = serde_json::from_slice(&lockfile_data)?;
+ let http_data: HttpData = serde_json::from_slice(&lockfile_data)?;
- let witness = ExtractorWitness {
+ // create witness data
+ let witness = HttpExtractorWitness {
data: input_data.clone(),
- keys: lockfile.as_bytes(),
+ http_data,
};
- if !args.output_dir.exists() {
- std::fs::create_dir_all(&args.output_dir)?;
+ // create witness dir
+ let mut output_dir = std::env::current_dir()?;
+ output_dir.push("inputs");
+ output_dir.push(&args.circuit_name);
+ if !output_dir.exists() {
+ std::fs::create_dir_all(&output_dir)?;
}
- let output_file = args.output_dir.join(args.output_filename);
+ // write witness to file
+ let output_file = output_dir.join("inputs.json");
let mut file = std::fs::File::create(output_file)?;
file.write_all(serde_json::to_string_pretty(&witness)?.as_bytes())?;
@@ -119,3 +176,10 @@ pub fn extractor_witness(args: ExtractorWitnessArgs) -> Result<(), Box Result<(), Box> {
+ match args.subcommand {
+ FileType::Json => json_extractor_witness(args),
+ FileType::Http => http_extractor_witness(args),
+ }
+}