Skip to content

Commit 2db860a

Browse files
authored
feat: add circomx (succinctlabs#237)
1 parent b4530d5 commit 2db860a

File tree

251 files changed

+3435
-2
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

251 files changed

+3435
-2
lines changed

.gitignore

+19-1
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,22 @@ keystore
2424

2525
.idea/
2626
proof.json
27-
output.json
27+
output.json
28+
29+
30+
.yarn/*
31+
!.yarn/patches
32+
!.yarn/plugins
33+
!.yarn/releases
34+
!.yarn/sdks
35+
!.yarn/versions
36+
37+
# Swap the comments on the following lines if you don't wish to use zero-installs
38+
# Documentation here: https://yarnpkg.com/features/zero-installs
39+
!.yarn/cache
40+
#.pnp.*
41+
42+
node_modules
43+
44+
dist/
45+
tsconfig.tsbuildinfo

.vscode/settings.json

+3-1
Original file line numberDiff line numberDiff line change
@@ -29,5 +29,7 @@
2929
],
3030
"rust-analyzer.diagnostics.disabled": [
3131
"unresolved-proc-macro"
32-
],
32+
],
33+
"editor.defaultFormatter": null,
34+
3335
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
23.8 KB
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

.yarnrc.yml

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
nodeLinker: node-modules

circomx/README.md

+9

circomx/package.json

+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
{
2+
"name": "@succinctlabs/circomx",
3+
"version": "0.0.13",
4+
"packageManager": "[email protected]",
5+
"type": "module",
6+
"exports": {
7+
".": {
8+
"import": "./dist/index.js"
9+
}
10+
},
11+
"typesVersions": {
12+
"*": {
13+
"*": [
14+
"*",
15+
"dist/*",
16+
"dist/*/index"
17+
]
18+
}
19+
},
20+
"types": "./dist/index.d.ts",
21+
"files": [
22+
"dist/**/*.d.ts",
23+
"dist/**/*.js",
24+
"dist/**/*.js.map",
25+
"*.d.ts",
26+
"*.js"
27+
],
28+
"dependencies": {
29+
"@chainsafe/persistent-merkle-tree": "^0.6.1",
30+
"@chainsafe/ssz": "^0.13.0",
31+
"@lodestar/api": "1.9.2",
32+
"@lodestar/config": "1.9.2",
33+
"@lodestar/types": "^1.11.3",
34+
"@noble/bls12-381": "^1.4.0",
35+
"@types/fnv-plus": "^1.3.0",
36+
"@types/node": "^20.4.6",
37+
"@types/yargs": "^17.0.26",
38+
"@vercel/ncc": "^0.38.0",
39+
"axios": "^1.5.1",
40+
"circomlib": "^2.0.5",
41+
"dotenv": "^16.3.1",
42+
"ethers": "5",
43+
"fnv-plus": "^1.3.1",
44+
"tsx": "^3.13.0",
45+
"typescript": "^5.2.2",
46+
"yargs": "^17.7.2"
47+
},
48+
"engines": {
49+
"node": "18.x"
50+
},
51+
"devDependencies": {
52+
"typescript": "^5.1.6"
53+
},
54+
"scripts": {
55+
"build": "tsc -b",
56+
"clean": "rm -rf dist && rm -f *.tsbuildinfo",
57+
"prepublishOnly": "yarn clean && yarn build"
58+
}
59+
}

circomx/src/bigint.ts

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { PointG1 } from "@noble/bls12-381";
2+
3+
export function pointToBigInt(point: PointG1): [bigint, bigint] {
4+
const [x, y] = point.toAffine();
5+
return [x.value, y.value];
6+
}
7+
8+
export function bigIntToArray(n: number, k: number, x: bigint) {
9+
let mod = 1n;
10+
for (let idx = 0; idx < n; idx++) {
11+
mod = mod * 2n;
12+
}
13+
14+
const ret: string[] = [];
15+
let x_temp: bigint = x;
16+
for (let idx = 0; idx < k; idx++) {
17+
ret.push((x_temp % mod).toString());
18+
x_temp = x_temp / mod;
19+
}
20+
return ret;
21+
}

circomx/src/circuit.ts

+227
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
1+
import { toHexString } from "@chainsafe/ssz";
2+
import dotenv from "dotenv";
3+
import fs from "fs";
4+
import yargs from "yargs";
5+
import { hideBin } from "yargs/helpers";
6+
import { encodeGroth16Proof, executeCommand } from "./util.js";
7+
import { CircomInput } from "./serializer.js";
8+
9+
const NODE_OPTIONS =
10+
"--huge-max-old-generation-size --max-old-space-size=2048000 --initial-old-space-size=2048000 --no-global-gc-scheduling --no-incremental-marking --max-semi-space-size=2048000 --initial-heap-size=2048000 --expose-gc";
11+
12+
// @ts-ignore
13+
BigInt.prototype.toJSON = function () {
14+
return this.toString();
15+
};
16+
17+
export type ProofData = {
18+
witness: CircomInput;
19+
outputBytes: Uint8Array;
20+
};
21+
22+
export abstract class Circuit {
23+
constructor() {}
24+
25+
abstract generateProofData(inputBytes: Buffer): Promise<ProofData>;
26+
27+
abstract circuitName(): string;
28+
29+
build(snarkjsPath: string, circomPath: string, ptauPath: string) {
30+
const circuit = this.circuitName();
31+
32+
// Create build dir if not exists
33+
if (!fs.existsSync("build")) {
34+
fs.mkdirSync("build");
35+
}
36+
37+
executeCommand(
38+
`${circomPath} circuits/${circuit}.circom --O1 --r1cs --sym --c --output build`
39+
);
40+
const circuitName = circuit === "main" ? "main_c" : circuit;
41+
const buildDirName = `${circuitName}_cpp`;
42+
executeCommand(`make -C build/${buildDirName}/`);
43+
executeCommand(
44+
`cp build/${buildDirName}/${circuitName} build/${circuitName}`
45+
);
46+
executeCommand(
47+
`cp build/${buildDirName}/${circuitName}.dat build/${circuitName}.dat`
48+
);
49+
executeCommand(`rm -rf build/${buildDirName}`);
50+
executeCommand(`rm -rf build/${circuit}_cpp`);
51+
executeCommand(
52+
`node ${NODE_OPTIONS} ${snarkjsPath} zkey new build/${circuitName}.r1cs ${ptauPath} build/p1.zkey`
53+
);
54+
executeCommand(
55+
`node ${NODE_OPTIONS} ${snarkjsPath} zkey export verificationkey build/p1.zkey build/vkey.json`
56+
);
57+
executeCommand(
58+
`node ${NODE_OPTIONS} ${snarkjsPath} zkey export solidityverifier build/p1.zkey build/FunctionVerifier.sol`
59+
);
60+
61+
// Replace first line of FunctionVerifier.sol with "pragma solidity ^0.8.0;"
62+
let solidityVerifier = fs.readFileSync(
63+
"build/FunctionVerifier.sol",
64+
"utf8"
65+
);
66+
solidityVerifier = solidityVerifier.replaceAll("calldataload", "mload");
67+
solidityVerifier = solidityVerifier.replaceAll("calldata", "memory");
68+
solidityVerifier = solidityVerifier.replaceAll(
69+
"_pB, _pC",
70+
// for some reason, uint256[2][2] memory _pB has two words (two lengths?) prepended to it
71+
// and calldata doesn't have it
72+
"add(_pB, 64), _pC"
73+
);
74+
solidityVerifier = solidityVerifier.replaceAll(
75+
"pragma solidity >=0.7.0 <0.9.0;",
76+
"pragma solidity ^0.8.16;"
77+
);
78+
solidityVerifier += `
79+
80+
interface IFunctionVerifier {
81+
function verify(bytes32 _inputHash, bytes32 _outputHash, bytes memory _proof) external view returns (bool);
82+
83+
function verificationKeyHash() external pure returns (bytes32);
84+
}
85+
86+
contract FunctionVerifier is IFunctionVerifier, Groth16Verifier {
87+
88+
function verify(bytes32 _inputHash, bytes32 _outputHash, bytes memory _proof) external view returns (bool) {
89+
(uint256[2] memory a, uint256[2][2] memory b, uint256[2] memory c) =
90+
abi.decode(_proof, (uint256[2], uint256[2][2], uint256[2]));
91+
92+
uint256[2] memory input = [uint256(_outputHash), uint256(_inputHash)];
93+
input[0] = input[0] & ((1 << 253) - 1);
94+
input[1] = input[1] & ((1 << 253) - 1);
95+
96+
return verifyProof(a, b, c, input);
97+
}
98+
99+
function verificationKeyHash() external pure returns (bytes32) {
100+
bytes memory left;
101+
bytes memory right;
102+
{
103+
left = abi.encode(alphax, alphay, betax1, betax2, betay1, betay2);
104+
}
105+
{
106+
right = abi.encode(gammax1, gammax2, gammay1, gammay2, deltax1, deltax2, deltay1, deltay2);
107+
}
108+
return keccak256(abi.encode(left, right));
109+
}
110+
}
111+
`;
112+
fs.writeFileSync("build/FunctionVerifier.sol", solidityVerifier);
113+
}
114+
115+
async prove(rapidsnarkPath: string, inputJsonPath: string) {
116+
const circuit = this.circuitName();
117+
const circuitName = circuit === "main" ? "main_c" : circuit;
118+
119+
const data = fs.readFileSync(inputJsonPath, "utf8");
120+
const jsonData = JSON.parse(data);
121+
console.log(jsonData);
122+
123+
let hexString = jsonData.data.input;
124+
125+
// Remove '0x' prefix if it exists
126+
if (hexString.startsWith("0x")) {
127+
hexString = hexString.slice(2);
128+
}
129+
130+
const buffer = Buffer.from(hexString, "hex");
131+
132+
const proofData = await this.generateProofData(buffer);
133+
134+
fs.writeFileSync("witness.json", JSON.stringify(proofData.witness));
135+
136+
executeCommand(`./build/${circuitName} witness.json witness.wtns`);
137+
executeCommand(
138+
`${rapidsnarkPath} build/p1.zkey witness.wtns proof.json public.json`
139+
);
140+
141+
const publicData = fs.readFileSync("public.json", "utf8");
142+
const publicJsonData = JSON.parse(publicData);
143+
console.log(publicJsonData);
144+
145+
const proofDataFile = fs.readFileSync("proof.json", "utf8");
146+
const proofJsonData = JSON.parse(proofDataFile);
147+
148+
// // TODO: sanity check circuit inputs
149+
// const circuitGeneratedInputs = publicJsonData.map((v: string) => {
150+
// const hex = BigInt(v).toString(16);
151+
// const paddedLen = Math.ceil(hex.length / 2) * 2;
152+
// return hex.padStart(paddedLen, "0");
153+
// });
154+
155+
const outputBytes = toHexString(proofData.outputBytes);
156+
157+
const proofBytes = encodeGroth16Proof(proofJsonData);
158+
const outputJson = {
159+
type: "res_bytes",
160+
data: {
161+
proof: proofBytes,
162+
output: outputBytes,
163+
},
164+
};
165+
166+
fs.writeFileSync("output.json", JSON.stringify(outputJson));
167+
console.log("Done");
168+
}
169+
170+
entrypoint() {
171+
dotenv.config();
172+
yargs(hideBin(process.argv))
173+
.command(
174+
"build",
175+
"Run build commands",
176+
(yargs) => {
177+
yargs
178+
.option("snarkjs", {
179+
describe: "Path to snarkjs cli.js",
180+
type: "string",
181+
default: "/root/snarkjs/cli.js",
182+
})
183+
.option("circom", {
184+
describe: "circom executable",
185+
type: "string",
186+
default: "circom",
187+
})
188+
.option("ptau", {
189+
describe: "Path to powersOfTau.ptau",
190+
type: "string",
191+
default: "/root/powersOfTau.ptau",
192+
});
193+
},
194+
(args) => {
195+
const snarkjsPath = args.snarkjs as string;
196+
const circomPath = args.circom as string;
197+
const ptauPath = args.ptau as string;
198+
this.build(snarkjsPath, circomPath, ptauPath);
199+
}
200+
)
201+
.command(
202+
"prove",
203+
"Run prove commands",
204+
(yargs) => {
205+
yargs
206+
.option("input-json", {
207+
describe: "Path to the input JSON file",
208+
type: "string",
209+
default: "input.json",
210+
})
211+
.option("rapidsnark", {
212+
describe: "Rapidsnark command",
213+
type: "string",
214+
default: "rapidsnark",
215+
});
216+
},
217+
async (args) => {
218+
const rapidsnarkPath = args.rapidsnark as string;
219+
const inputJsonPath = args["input-json"] as string;
220+
221+
await this.prove(rapidsnarkPath, inputJsonPath);
222+
}
223+
)
224+
.demandCommand(1, "You need to provide a command (either build or prove)")
225+
.parse();
226+
}
227+
}

0 commit comments

Comments
 (0)