starkshield 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +101 -0
- package/bin/circom +0 -0
- package/bin/cli.js +36 -0
- package/bin/index.js +13 -0
- package/circomlib/.vscode/launch.json +63 -0
- package/circomlib/README.md +18 -0
- package/circomlib/circuits/README.md +830 -0
- package/circomlib/circuits/aliascheck.circom +33 -0
- package/circomlib/circuits/babyjub.circom +107 -0
- package/circomlib/circuits/binsub.circom +74 -0
- package/circomlib/circuits/binsum.circom +101 -0
- package/circomlib/circuits/bitify.circom +106 -0
- package/circomlib/circuits/comparators.circom +141 -0
- package/circomlib/circuits/compconstant.circom +74 -0
- package/circomlib/circuits/eddsa.circom +139 -0
- package/circomlib/circuits/eddsamimc.circom +124 -0
- package/circomlib/circuits/eddsamimcsponge.circom +124 -0
- package/circomlib/circuits/eddsaposeidon.circom +123 -0
- package/circomlib/circuits/escalarmul.circom +166 -0
- package/circomlib/circuits/escalarmulany.circom +197 -0
- package/circomlib/circuits/escalarmulfix.circom +299 -0
- package/circomlib/circuits/escalarmulw4table.circom +52 -0
- package/circomlib/circuits/gates.circom +96 -0
- package/circomlib/circuits/mimc.circom +156 -0
- package/circomlib/circuits/mimcsponge.circom +293 -0
- package/circomlib/circuits/montgomery.circom +142 -0
- package/circomlib/circuits/multiplexer.circom +115 -0
- package/circomlib/circuits/mux1.circom +48 -0
- package/circomlib/circuits/mux2.circom +63 -0
- package/circomlib/circuits/mux3.circom +75 -0
- package/circomlib/circuits/mux4.circom +119 -0
- package/circomlib/circuits/pedersen.circom +257 -0
- package/circomlib/circuits/pedersen_old.circom +68 -0
- package/circomlib/circuits/pointbits.circom +164 -0
- package/circomlib/circuits/poseidon.circom +208 -0
- package/circomlib/circuits/poseidon_constants.circom +24959 -0
- package/circomlib/circuits/poseidon_constants_old.circom +252 -0
- package/circomlib/circuits/poseidon_old.circom +97 -0
- package/circomlib/circuits/sha256/ch.circom +47 -0
- package/circomlib/circuits/sha256/constants.circom +53 -0
- package/circomlib/circuits/sha256/main.circom +35 -0
- package/circomlib/circuits/sha256/maj.circom +45 -0
- package/circomlib/circuits/sha256/rotate.circom +28 -0
- package/circomlib/circuits/sha256/sha256.circom +81 -0
- package/circomlib/circuits/sha256/sha256_2.circom +91 -0
- package/circomlib/circuits/sha256/sha256compression.circom +166 -0
- package/circomlib/circuits/sha256/sha256compression_function.circom +112 -0
- package/circomlib/circuits/sha256/shift.circom +33 -0
- package/circomlib/circuits/sha256/sigma.circom +77 -0
- package/circomlib/circuits/sha256/sigmaplus.circom +50 -0
- package/circomlib/circuits/sha256/t1.circom +58 -0
- package/circomlib/circuits/sha256/t2.circom +51 -0
- package/circomlib/circuits/sha256/xor3.circom +45 -0
- package/circomlib/circuits/sign.circom +36 -0
- package/circomlib/circuits/smt/smthash_mimc.circom +58 -0
- package/circomlib/circuits/smt/smthash_poseidon.circom +57 -0
- package/circomlib/circuits/smt/smtlevins.circom +103 -0
- package/circomlib/circuits/smt/smtprocessor.circom +261 -0
- package/circomlib/circuits/smt/smtprocessorlevel.circom +95 -0
- package/circomlib/circuits/smt/smtprocessorsm.circom +165 -0
- package/circomlib/circuits/smt/smtverifier.circom +138 -0
- package/circomlib/circuits/smt/smtverifierlevel.circom +71 -0
- package/circomlib/circuits/smt/smtverifiersm.circom +106 -0
- package/circomlib/circuits/switcher.circom +42 -0
- package/circomlib/doc/root_transfer.monopic +0 -0
- package/circomlib/doc/smt_diagram_0.monopic +0 -0
- package/circomlib/doc/smt_diagram_1.monopic +0 -0
- package/circomlib/doc/smt_hash.monopic +0 -0
- package/circomlib/doc/smt_levins.monopic +0 -0
- package/circomlib/doc/smt_sm.monopic +0 -0
- package/circomlib/doc/smt_verifier_sm.monopic +0 -0
- package/circomlib/doc/voting.monopic +0 -0
- package/circomlib/doc/window.monopic +0 -0
- package/circomlib/doc/window_chain.monopic +0 -0
- package/circomlib/index.js +2 -0
- package/circomlib/package.json +33 -0
- package/circomlib/test/aliascheck.js +77 -0
- package/circomlib/test/babyjub.js +118 -0
- package/circomlib/test/binsub.js +52 -0
- package/circomlib/test/binsum.js +38 -0
- package/circomlib/test/circuits/aliascheck_test.circom +4 -0
- package/circomlib/test/circuits/babyadd_tester.circom +4 -0
- package/circomlib/test/circuits/babycheck_test.circom +4 -0
- package/circomlib/test/circuits/babypbk_test.circom +4 -0
- package/circomlib/test/circuits/binsub_test.circom +33 -0
- package/circomlib/test/circuits/constants_test.circom +20 -0
- package/circomlib/test/circuits/eddsa_test.circom +5 -0
- package/circomlib/test/circuits/eddsamimc_test.circom +5 -0
- package/circomlib/test/circuits/eddsaposeidon_test.circom +5 -0
- package/circomlib/test/circuits/edwards2montgomery.circom +5 -0
- package/circomlib/test/circuits/escalarmul_min_test.circom +27 -0
- package/circomlib/test/circuits/escalarmul_test.circom +33 -0
- package/circomlib/test/circuits/escalarmul_test_min.circom +28 -0
- package/circomlib/test/circuits/escalarmulany_test.circom +30 -0
- package/circomlib/test/circuits/escalarmulfix_test.circom +31 -0
- package/circomlib/test/circuits/escalarmulw4table.circom +20 -0
- package/circomlib/test/circuits/escalarmulw4table_test.circom +19 -0
- package/circomlib/test/circuits/escalarmulw4table_test3.circom +19 -0
- package/circomlib/test/circuits/greatereqthan.circom +5 -0
- package/circomlib/test/circuits/greaterthan.circom +5 -0
- package/circomlib/test/circuits/isequal.circom +5 -0
- package/circomlib/test/circuits/iszero.circom +5 -0
- package/circomlib/test/circuits/lesseqthan.circom +5 -0
- package/circomlib/test/circuits/lessthan.circom +5 -0
- package/circomlib/test/circuits/mimc_sponge_hash_test.circom +5 -0
- package/circomlib/test/circuits/mimc_sponge_test.circom +5 -0
- package/circomlib/test/circuits/mimc_test.circom +5 -0
- package/circomlib/test/circuits/montgomery2edwards.circom +5 -0
- package/circomlib/test/circuits/montgomeryadd.circom +5 -0
- package/circomlib/test/circuits/montgomerydouble.circom +5 -0
- package/circomlib/test/circuits/mux1_1.circom +33 -0
- package/circomlib/test/circuits/mux2_1.circom +37 -0
- package/circomlib/test/circuits/mux3_1.circom +41 -0
- package/circomlib/test/circuits/mux4_1.circom +56 -0
- package/circomlib/test/circuits/pedersen2_test.circom +34 -0
- package/circomlib/test/circuits/pedersen_test.circom +31 -0
- package/circomlib/test/circuits/pointbits_loopback.circom +25 -0
- package/circomlib/test/circuits/poseidon3_test.circom +5 -0
- package/circomlib/test/circuits/poseidon6_test.circom +5 -0
- package/circomlib/test/circuits/poseidonex_test.circom +5 -0
- package/circomlib/test/circuits/sha256_2_test.circom +17 -0
- package/circomlib/test/circuits/sha256_test448.circom +5 -0
- package/circomlib/test/circuits/sha256_test512.circom +5 -0
- package/circomlib/test/circuits/sign_test.circom +5 -0
- package/circomlib/test/circuits/smtprocessor10_test.circom +5 -0
- package/circomlib/test/circuits/smtverifier10_test.circom +5 -0
- package/circomlib/test/circuits/sum_test.circom +33 -0
- package/circomlib/test/comparators.js +187 -0
- package/circomlib/test/eddsa.js +75 -0
- package/circomlib/test/eddsamimc.js +102 -0
- package/circomlib/test/eddsaposeidon.js +103 -0
- package/circomlib/test/escalarmul.js +121 -0
- package/circomlib/test/escalarmulany.js +51 -0
- package/circomlib/test/escalarmulfix.js +95 -0
- package/circomlib/test/helpers/printsignal.js +22 -0
- package/circomlib/test/helpers/sha256.js +178 -0
- package/circomlib/test/mimccircuit.js +27 -0
- package/circomlib/test/mimcspongecircuit.js +47 -0
- package/circomlib/test/montgomery.js +101 -0
- package/circomlib/test/multiplexer.js +101 -0
- package/circomlib/test/pedersen.js +83 -0
- package/circomlib/test/pedersen2.js +56 -0
- package/circomlib/test/point2bits.js +30 -0
- package/circomlib/test/poseidoncircuit.js +80 -0
- package/circomlib/test/sha256.js +118 -0
- package/circomlib/test/sign.js +82 -0
- package/circomlib/test/smtprocessor.js +219 -0
- package/circomlib/test/smtverifier.js +141 -0
- package/lib/compile.js +82 -0
- package/lib/deploy.js +391 -0
- package/lib/test.js +47 -0
- package/lib/verify.js +153 -0
- package/package.json +43 -0
- package/pot12_0000.ptau +0 -0
- package/pot12_0001.ptau +0 -0
- package/ptau/pot12_final.ptau +0 -0
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
const chai = require("chai");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
const Scalar = require("ffjavascript").Scalar;
|
|
4
|
+
const wasm_tester = require("circom_tester").wasm;
|
|
5
|
+
|
|
6
|
+
const newMemEmptyTrie = require("circomlibjs").newMemEmptyTrie;
|
|
7
|
+
|
|
8
|
+
const assert = chai.assert;
|
|
9
|
+
|
|
10
|
+
function print(circuit, w, s) {
|
|
11
|
+
console.log(s + ": " + w[circuit.getSignalIdx(s)]);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async function testInclusion(tree, _key, circuit) {
|
|
15
|
+
const key = tree.F.e(_key);
|
|
16
|
+
const res = await tree.find(key);
|
|
17
|
+
|
|
18
|
+
assert(res.found);
|
|
19
|
+
let siblings = res.siblings;
|
|
20
|
+
for (let i=0; i<siblings.length; i++) siblings[i] = tree.F.toObject(siblings[i]);
|
|
21
|
+
while (siblings.length<10) siblings.push(0);
|
|
22
|
+
|
|
23
|
+
const w = await circuit.calculateWitness({
|
|
24
|
+
enabled: 1,
|
|
25
|
+
fnc: 0,
|
|
26
|
+
root: tree.F.toObject(tree.root),
|
|
27
|
+
siblings: siblings,
|
|
28
|
+
oldKey: 0,
|
|
29
|
+
oldValue: 0,
|
|
30
|
+
isOld0: 0,
|
|
31
|
+
key: tree.F.toObject(key),
|
|
32
|
+
value: tree.F.toObject(res.foundValue)
|
|
33
|
+
}, true);
|
|
34
|
+
|
|
35
|
+
await circuit.checkConstraints(w);
|
|
36
|
+
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async function testExclusion(tree, _key, circuit) {
|
|
40
|
+
const key = tree.F.e(_key);
|
|
41
|
+
const res = await tree.find(key);
|
|
42
|
+
|
|
43
|
+
assert(!res.found);
|
|
44
|
+
let siblings = res.siblings;
|
|
45
|
+
for (let i=0; i<siblings.length; i++) siblings[i] = tree.F.toObject(siblings[i]);
|
|
46
|
+
while (siblings.length<10) siblings.push(0);
|
|
47
|
+
|
|
48
|
+
const w = await circuit.calculateWitness({
|
|
49
|
+
enabled: 1,
|
|
50
|
+
fnc: 1,
|
|
51
|
+
root: tree.F.toObject(tree.root),
|
|
52
|
+
siblings: siblings,
|
|
53
|
+
oldKey: res.isOld0 ? 0 : tree.F.toObject(res.notFoundKey),
|
|
54
|
+
oldValue: res.isOld0 ? 0 : tree.F.toObject(res.notFoundValue),
|
|
55
|
+
isOld0: res.isOld0 ? 1 : 0,
|
|
56
|
+
key: tree.F.toObject(key),
|
|
57
|
+
value: 0
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
await circuit.checkConstraints(w);
|
|
61
|
+
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
describe("SMT Verifier test", function () {
|
|
65
|
+
let Fr;
|
|
66
|
+
let circuit;
|
|
67
|
+
let tree;
|
|
68
|
+
|
|
69
|
+
this.timeout(100000);
|
|
70
|
+
|
|
71
|
+
before( async () => {
|
|
72
|
+
circuit = await wasm_tester(path.join(__dirname, "circuits", "smtverifier10_test.circom"));
|
|
73
|
+
|
|
74
|
+
tree = await newMemEmptyTrie();
|
|
75
|
+
Fr = tree.F;
|
|
76
|
+
await tree.insert(7,77);
|
|
77
|
+
await tree.insert(8,88);
|
|
78
|
+
await tree.insert(32,3232);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it("Check inclussion in a tree of 3", async () => {
|
|
82
|
+
await testInclusion(tree, 7, circuit);
|
|
83
|
+
await testInclusion(tree, 8, circuit);
|
|
84
|
+
await testInclusion(tree, 32, circuit);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it("Check exclussion in a tree of 3", async () => {
|
|
88
|
+
await testExclusion(tree, 0, circuit);
|
|
89
|
+
await testExclusion(tree, 6, circuit);
|
|
90
|
+
await testExclusion(tree, 9, circuit);
|
|
91
|
+
await testExclusion(tree, 33, circuit);
|
|
92
|
+
await testExclusion(tree, 31, circuit);
|
|
93
|
+
await testExclusion(tree, 16, circuit);
|
|
94
|
+
await testExclusion(tree, 64, circuit);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it("Check not enabled accepts any thing", async () => {
|
|
98
|
+
let siblings = [];
|
|
99
|
+
for (let i=0; i<10; i++) siblings.push(i);
|
|
100
|
+
|
|
101
|
+
const w = await circuit.calculateWitness({
|
|
102
|
+
enabled: 0,
|
|
103
|
+
fnc: 0,
|
|
104
|
+
root: 1,
|
|
105
|
+
siblings: siblings,
|
|
106
|
+
oldKey: 22,
|
|
107
|
+
oldValue: 33,
|
|
108
|
+
isOld0: 0,
|
|
109
|
+
key: 44,
|
|
110
|
+
value: 0
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
await circuit.checkConstraints(w);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it("Check inclussion Adria case", async () => {
|
|
118
|
+
const e1_hi= Fr.e("17124152697573569611556136390143205198134245887034837071647643529178599000839");
|
|
119
|
+
const e1_hv= Fr.e("19650379996168153643111744440707177573540245771926102415571667548153444658179");
|
|
120
|
+
|
|
121
|
+
const e2ok_hi= Fr.e("16498254692537945203721083102154618658340563351558973077349594629411025251262");
|
|
122
|
+
const e2ok_hv= Fr.e("19650379996168153643111744440707177573540245771926102415571667548153444658179");
|
|
123
|
+
|
|
124
|
+
const e2fail_hi= Fr.e("17195092312975762537892237130737365903429674363577646686847513978084990105579");
|
|
125
|
+
const e2fail_hv= Fr.e("19650379996168153643111744440707177573540245771926102415571667548153444658179");
|
|
126
|
+
|
|
127
|
+
const tree1 = await newMemEmptyTrie();
|
|
128
|
+
await tree1.insert(e1_hi,e1_hv);
|
|
129
|
+
await tree1.insert(e2ok_hi,e2ok_hv);
|
|
130
|
+
|
|
131
|
+
await testInclusion(tree1, e2ok_hi, circuit);
|
|
132
|
+
|
|
133
|
+
const tree2 = await newMemEmptyTrie();
|
|
134
|
+
await tree2.insert(e1_hi,e1_hv);
|
|
135
|
+
await tree2.insert(e2fail_hi,e2fail_hv);
|
|
136
|
+
|
|
137
|
+
await testInclusion(tree2, e2fail_hi, circuit);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
});
|
package/lib/compile.js
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
const { execSync } = require("child_process");
|
|
2
|
+
const fs = require("fs-extra");
|
|
3
|
+
const path = require("path");
|
|
4
|
+
|
|
5
|
+
async function compileCircuit(circuitPath) {
|
|
6
|
+
const baseName = path.basename(circuitPath, ".circom");
|
|
7
|
+
const outDir = path.resolve(path.join(process.cwd(), baseName));
|
|
8
|
+
|
|
9
|
+
await fs.ensureDir(outDir);
|
|
10
|
+
await fs.emptyDir(outDir);
|
|
11
|
+
|
|
12
|
+
console.log(`📦 Compiling ${baseName} into ${outDir}...`);
|
|
13
|
+
|
|
14
|
+
// Get absolute paths for better reliability
|
|
15
|
+
const absCircuitPath = path.resolve(circuitPath);
|
|
16
|
+
const circomlibPath = path.resolve(__dirname, "..", "circomlib", "circuits");
|
|
17
|
+
|
|
18
|
+
// Verify circomlib exists
|
|
19
|
+
if (!fs.existsSync(circomlibPath)) {
|
|
20
|
+
throw new Error(`❌ Circomlib not found at: ${circomlibPath}`);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Define output paths
|
|
24
|
+
const r1csPath = path.join(outDir, `${baseName}.r1cs`);
|
|
25
|
+
const zkeyPath = path.join(outDir, `circuit_final.zkey`);
|
|
26
|
+
const verifierPath = path.join(outDir, `verifier.cairo`); // Changed to Cairo (the main contract will be copied here for easy use in further modules)
|
|
27
|
+
|
|
28
|
+
// Compile circuit with circomlib path
|
|
29
|
+
execSync(
|
|
30
|
+
`"${path.resolve(__dirname, "..", "bin", "circom")}" "${absCircuitPath}" --wasm --r1cs -l "${circomlibPath}" -o "${outDir}"`,
|
|
31
|
+
{
|
|
32
|
+
stdio: "inherit",
|
|
33
|
+
cwd: outDir
|
|
34
|
+
}
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
// Verify R1CS file exists
|
|
38
|
+
if (!fs.existsSync(r1csPath)) {
|
|
39
|
+
throw new Error(`❌ Compilation failed: ${r1csPath} not found.`);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Rest of your existing code...
|
|
43
|
+
const ptauPath = path.resolve(__dirname, "..", "ptau", "pot12_final.ptau");
|
|
44
|
+
if (!fs.existsSync(ptauPath)) {
|
|
45
|
+
throw new Error(`❌ Missing PTAU file at: ${ptauPath}`);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Run groth16 setup
|
|
49
|
+
execSync(
|
|
50
|
+
`snarkjs groth16 setup "${r1csPath}" "${ptauPath}" "${zkeyPath}"`,
|
|
51
|
+
{ stdio: "inherit" }
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
// === MODIFIED PART: Generate Cairo verifier for Starknet instead of Solidity ===
|
|
55
|
+
// 1. Export verification key (required by garaga)
|
|
56
|
+
execSync(
|
|
57
|
+
`snarkjs zkey export verificationkey "${zkeyPath}" "${outDir}/verification_key.json"`,
|
|
58
|
+
{ stdio: "inherit" }
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
// 2. Generate valid Cairo Groth16 verifier contract using garaga (state-of-the-art, actively maintained, handles BN254 correctly)
|
|
62
|
+
// Prerequisites (run once):
|
|
63
|
+
// pip install garaga
|
|
64
|
+
// (Scarb is auto-detected; install via https://docs.swmansion.com/scarb/ if you want to build/deploy the contract)
|
|
65
|
+
console.log("🔨 Generating Cairo verifier contract with garaga...");
|
|
66
|
+
execSync(
|
|
67
|
+
`garaga gen --system groth16 --vk "${outDir}/verification_key.json" --project-name "${baseName}"`,
|
|
68
|
+
{
|
|
69
|
+
stdio: "inherit",
|
|
70
|
+
cwd: outDir
|
|
71
|
+
}
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
// garaga creates a full Scarb project in ./verifier/ by default.
|
|
75
|
+
// We copy the main contract file to verifier.cairo (so further modules that expect "verifier.sol" style can just rename/use verifier.cairo)
|
|
76
|
+
const generatedContract = path.join(outDir, "verifier", "src", "groth16_verifier.cairo");
|
|
77
|
+
|
|
78
|
+
console.log(`✅ All files successfully generated in: ${outDir}`);
|
|
79
|
+
console.log(` → Cairo verifier ready at: ${generatedContract}`);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
module.exports = { compileCircuit };
|
package/lib/deploy.js
ADDED
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs-extra');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { spawnSync } = require('child_process');
|
|
6
|
+
|
|
7
|
+
// ─── Constants ────────────────────────────────────────────────────────────────
|
|
8
|
+
|
|
9
|
+
const NETWORK = 'sepolia';
|
|
10
|
+
const CONTRACT_NAME = 'Groth16VerifierBN254';
|
|
11
|
+
|
|
12
|
+
// ─── Core runner ──────────────────────────────────────────────────────────────
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Run a shell command using spawnSync so we always capture BOTH stdout and
|
|
16
|
+
* stderr. sncast prints its results (class hash, contract address) to stderr
|
|
17
|
+
* in many versions, so execSync/stdout-only is not reliable.
|
|
18
|
+
*
|
|
19
|
+
* Returns { stdout, stderr, combined, status }.
|
|
20
|
+
* Does NOT throw — callers check status and combined themselves.
|
|
21
|
+
*/
|
|
22
|
+
function run(cmd, cwd) {
|
|
23
|
+
// Split the command string into argv array for spawnSync.
|
|
24
|
+
// We use shell:true to support multi-line commands with backslash continuation.
|
|
25
|
+
const result = spawnSync(cmd, {
|
|
26
|
+
shell: true,
|
|
27
|
+
encoding: 'utf8',
|
|
28
|
+
cwd: cwd || process.cwd(),
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
const stdout = result.stdout || '';
|
|
32
|
+
const stderr = result.stderr || '';
|
|
33
|
+
const combined = stdout + stderr;
|
|
34
|
+
|
|
35
|
+
return {
|
|
36
|
+
stdout,
|
|
37
|
+
stderr,
|
|
38
|
+
combined,
|
|
39
|
+
status: result.status ?? 1,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// ─── Path resolution ──────────────────────────────────────────────────────────
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Resolve and validate the folder structure that compile produces:
|
|
47
|
+
*
|
|
48
|
+
* <folderPath>/ ← absFolder (e.g. ./simple)
|
|
49
|
+
* ├── simple.r1cs
|
|
50
|
+
* ├── circuit_final.zkey
|
|
51
|
+
* ├── verification_key.json
|
|
52
|
+
* ├── simple_js/simple.wasm
|
|
53
|
+
* └── simple/ ← scarbProjectDir (e.g. ./simple/simple)
|
|
54
|
+
* ├── Scarb.toml
|
|
55
|
+
* ├── src/
|
|
56
|
+
* └── tests/
|
|
57
|
+
*/
|
|
58
|
+
function resolvePaths(folderPath) {
|
|
59
|
+
const absFolder = path.resolve(folderPath);
|
|
60
|
+
const circuitName = path.basename(absFolder);
|
|
61
|
+
const scarbProjectDir = path.join(absFolder, circuitName);
|
|
62
|
+
|
|
63
|
+
if (!fs.existsSync(absFolder)) {
|
|
64
|
+
console.error(`❌ Output folder not found: ${absFolder}`);
|
|
65
|
+
console.error(' Run "npx starkshield compile <circuit.circom>" first.');
|
|
66
|
+
process.exit(1);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const expectedFiles = [
|
|
70
|
+
path.join(absFolder, 'verification_key.json'),
|
|
71
|
+
path.join(absFolder, 'circuit_final.zkey'),
|
|
72
|
+
path.join(absFolder, `${circuitName}.r1cs`),
|
|
73
|
+
];
|
|
74
|
+
for (const f of expectedFiles) {
|
|
75
|
+
if (!fs.existsSync(f)) {
|
|
76
|
+
console.error(`❌ Missing compile artefact: ${f}`);
|
|
77
|
+
console.error(' Re-run "npx starkshield compile <circuit.circom>".');
|
|
78
|
+
process.exit(1);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (!fs.existsSync(path.join(scarbProjectDir, 'Scarb.toml'))) {
|
|
83
|
+
console.error(`❌ Scarb.toml not found in: ${scarbProjectDir}`);
|
|
84
|
+
console.error(' Re-run "npx starkshield compile <circuit.circom>".');
|
|
85
|
+
process.exit(1);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return { absFolder, circuitName, scarbProjectDir };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// ─── Account management ───────────────────────────────────────────────────────
|
|
92
|
+
|
|
93
|
+
function importAccount(accountName, privateKey, accountAddress) {
|
|
94
|
+
console.log(`\n🔑 Registering account "${accountName}" with sncast ...`);
|
|
95
|
+
|
|
96
|
+
// Remove stale entry silently.
|
|
97
|
+
run(`sncast account delete --name ${accountName} --network-name alpha-${NETWORK}`);
|
|
98
|
+
|
|
99
|
+
const { combined, status } = run(
|
|
100
|
+
`sncast account import \
|
|
101
|
+
--network ${NETWORK} \
|
|
102
|
+
--name ${accountName} \
|
|
103
|
+
--address ${accountAddress} \
|
|
104
|
+
--private-key ${privateKey} \
|
|
105
|
+
--type oz`,
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
if (status !== 0 && !combined.toLowerCase().includes('already')) {
|
|
109
|
+
console.error('❌ Failed to register account with sncast:');
|
|
110
|
+
console.error(combined);
|
|
111
|
+
process.exit(1);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
console.log('✅ Account registered.');
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function cleanupAccount(accountName) {
|
|
118
|
+
const { status } = run(
|
|
119
|
+
`sncast account delete --name ${accountName} --network-name alpha-${NETWORK}`,
|
|
120
|
+
);
|
|
121
|
+
if (status === 0) {
|
|
122
|
+
console.log(`🧹 Temporary account "${accountName}" removed.`);
|
|
123
|
+
} else {
|
|
124
|
+
console.warn(`⚠️ Could not remove temporary account "${accountName}" — remove it manually if needed.`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// ─── Output parsers ───────────────────────────────────────────────────────────
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Extract a class hash from any sncast declare output format:
|
|
132
|
+
*
|
|
133
|
+
* JSON: { "class_hash": "0x..." }
|
|
134
|
+
* Labeled: class_hash: 0x...
|
|
135
|
+
* Parenthesised: ClassHash(0x...)
|
|
136
|
+
* Error sentence: "with hash 0x..."
|
|
137
|
+
* Fallback: any 0x value that is 60-64 hex chars (full felt252)
|
|
138
|
+
*/
|
|
139
|
+
function extractClassHash(text) {
|
|
140
|
+
if (!text) return null;
|
|
141
|
+
|
|
142
|
+
try {
|
|
143
|
+
const data = JSON.parse(text.trim());
|
|
144
|
+
const h = data.class_hash || data.result?.class_hash;
|
|
145
|
+
if (h) return h;
|
|
146
|
+
} catch (_) {}
|
|
147
|
+
|
|
148
|
+
const patterns = [
|
|
149
|
+
/class[_\s-]hash[\s:=]+\s*(0x[0-9a-fA-F]+)/i,
|
|
150
|
+
/[Cc]lass[Hh]ash\s*\(\s*(0x[0-9a-fA-F]+)\s*\)/,
|
|
151
|
+
/with\s+hash\s+(0x[0-9a-fA-F]+)/i,
|
|
152
|
+
/hash[:\s]+(0x[0-9a-fA-F]+)/i,
|
|
153
|
+
];
|
|
154
|
+
for (const re of patterns) {
|
|
155
|
+
const m = text.match(re);
|
|
156
|
+
if (m) return m[1];
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Any full felt252-length hex value
|
|
160
|
+
const all = [...text.matchAll(/0x[0-9a-fA-F]{60,64}/g)];
|
|
161
|
+
if (all.length > 0) return all[0][0];
|
|
162
|
+
|
|
163
|
+
return null;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Extract a deployed contract address from sncast deploy output.
|
|
168
|
+
*/
|
|
169
|
+
function extractContractAddress(text) {
|
|
170
|
+
if (!text) return null;
|
|
171
|
+
|
|
172
|
+
try {
|
|
173
|
+
const data = JSON.parse(text.trim());
|
|
174
|
+
const a = data.contract_address || data.address || data.result?.contract_address;
|
|
175
|
+
if (a) return a;
|
|
176
|
+
} catch (_) {}
|
|
177
|
+
|
|
178
|
+
const patterns = [
|
|
179
|
+
/contract[_\s]address[\s:=]+\s*(0x[0-9a-fA-F]+)/i,
|
|
180
|
+
/address[\s:=]+\s*(0x[0-9a-fA-F]+)/i,
|
|
181
|
+
];
|
|
182
|
+
for (const re of patterns) {
|
|
183
|
+
const m = text.match(re);
|
|
184
|
+
if (m) return m[1];
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const all = [...text.matchAll(/0x[0-9a-fA-F]{60,64}/g)];
|
|
188
|
+
if (all.length > 0) return all[0][0];
|
|
189
|
+
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// ─── ABI extraction ───────────────────────────────────────────────────────────
|
|
194
|
+
|
|
195
|
+
function extractAbi(scarbProjectDir) {
|
|
196
|
+
const targetDevDir = path.join(scarbProjectDir, 'target', 'dev');
|
|
197
|
+
|
|
198
|
+
if (!fs.existsSync(targetDevDir)) {
|
|
199
|
+
throw new Error(`Build artefacts not found at: ${targetDevDir}`);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const files = fs.readdirSync(targetDevDir);
|
|
203
|
+
const contractFile = files.find(
|
|
204
|
+
(f) => f.endsWith('.contract_class.json') || f.endsWith('.sierra.json'),
|
|
205
|
+
);
|
|
206
|
+
|
|
207
|
+
if (!contractFile) {
|
|
208
|
+
throw new Error(
|
|
209
|
+
`No .contract_class.json found in ${targetDevDir}.\nFiles: ${files.join(', ')}`,
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const sierra = fs.readJsonSync(path.join(targetDevDir, contractFile));
|
|
214
|
+
if (!sierra.abi || sierra.abi.length === 0) {
|
|
215
|
+
throw new Error(`ABI is empty in ${contractFile}.`);
|
|
216
|
+
}
|
|
217
|
+
return sierra.abi;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// ─── Main export ──────────────────────────────────────────────────────────────
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Build, declare, and deploy the Garaga Groth16 verifier to Starknet Sepolia.
|
|
224
|
+
*
|
|
225
|
+
* Writes <folderPath>/deployment.json on success with everything verifyProof needs.
|
|
226
|
+
*
|
|
227
|
+
* @param {string} folderPath Root circuit output folder (e.g. ./simple)
|
|
228
|
+
* @param {string} privateKey Starknet account private key (0x-prefixed)
|
|
229
|
+
* @param {string} accountAddress Starknet account address (0x-prefixed)
|
|
230
|
+
*/
|
|
231
|
+
async function deployVerifier(folderPath, privateKey, accountAddress) {
|
|
232
|
+
|
|
233
|
+
if (!folderPath || !privateKey || !accountAddress) {
|
|
234
|
+
console.error('❌ Usage: npx starkshield deploy <folder> <privateKey> <accountAddress>');
|
|
235
|
+
process.exit(1);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// ── 1. Validate folder structure ───────────────────────────────────────────
|
|
239
|
+
|
|
240
|
+
const { absFolder, circuitName, scarbProjectDir } = resolvePaths(folderPath);
|
|
241
|
+
|
|
242
|
+
console.log('');
|
|
243
|
+
console.log('🛡 StarkShield — Deploy');
|
|
244
|
+
console.log(` Circuit : ${circuitName}`);
|
|
245
|
+
console.log(` Output folder: ${absFolder}`);
|
|
246
|
+
console.log(` Scarb project: ${scarbProjectDir}`);
|
|
247
|
+
console.log(` Network : ${NETWORK}`);
|
|
248
|
+
|
|
249
|
+
// ── 2. Register account ────────────────────────────────────────────────────
|
|
250
|
+
|
|
251
|
+
const accountName = `starkshield-${circuitName}-${Date.now()}`;
|
|
252
|
+
importAccount(accountName, privateKey, accountAddress);
|
|
253
|
+
|
|
254
|
+
try {
|
|
255
|
+
|
|
256
|
+
// ── 3. Build ───────────────────────────────────────────────────────────────
|
|
257
|
+
|
|
258
|
+
console.log(`\n🔨 Building Cairo verifier (scarb build) ...`);
|
|
259
|
+
const build = run('scarb build', scarbProjectDir);
|
|
260
|
+
if (build.status !== 0) {
|
|
261
|
+
console.error('❌ scarb build failed:');
|
|
262
|
+
console.error(build.combined);
|
|
263
|
+
process.exit(1);
|
|
264
|
+
}
|
|
265
|
+
console.log('✅ Build complete.');
|
|
266
|
+
|
|
267
|
+
// ── 4. Extract ABI ────────────────────────────────────────────────────────
|
|
268
|
+
|
|
269
|
+
console.log('\n📄 Extracting ABI from build artefacts ...');
|
|
270
|
+
const abi = extractAbi(scarbProjectDir);
|
|
271
|
+
console.log(`✅ ABI extracted (${abi.length} entries).`);
|
|
272
|
+
|
|
273
|
+
// ── 5. Declare ────────────────────────────────────────────────────────────
|
|
274
|
+
|
|
275
|
+
console.log(`\n📢 Declaring ${CONTRACT_NAME} on ${NETWORK} ...`);
|
|
276
|
+
let classHash;
|
|
277
|
+
|
|
278
|
+
const declare = run(
|
|
279
|
+
`sncast --account ${accountName} \
|
|
280
|
+
declare \
|
|
281
|
+
--contract-name ${CONTRACT_NAME} \
|
|
282
|
+
--network ${NETWORK}`,
|
|
283
|
+
scarbProjectDir,
|
|
284
|
+
);
|
|
285
|
+
|
|
286
|
+
console.log('── sncast declare stdout ──────────────────────────────────');
|
|
287
|
+
console.log(declare.stdout || '(empty)');
|
|
288
|
+
console.log('── sncast declare stderr ──────────────────────────────────');
|
|
289
|
+
console.log(declare.stderr || '(empty)');
|
|
290
|
+
console.log('───────────────────────────────────────────────────────────');
|
|
291
|
+
|
|
292
|
+
const declareText = declare.combined;
|
|
293
|
+
const alreadyDeclared =
|
|
294
|
+
declareText.toLowerCase().includes('already declared') ||
|
|
295
|
+
declareText.toLowerCase().includes('class hash already exists') ||
|
|
296
|
+
declareText.toLowerCase().includes('contract class already declared');
|
|
297
|
+
|
|
298
|
+
if (declare.status === 0 || alreadyDeclared) {
|
|
299
|
+
classHash = extractClassHash(declareText);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
if (!classHash && alreadyDeclared) {
|
|
303
|
+
// Class is on-chain but we couldn't get the hash from the error — compute locally.
|
|
304
|
+
console.log('ℹ️ Class already on-chain. Computing hash locally ...');
|
|
305
|
+
const hashResult = run(
|
|
306
|
+
`sncast class-hash --contract-name ${CONTRACT_NAME}`,
|
|
307
|
+
scarbProjectDir,
|
|
308
|
+
);
|
|
309
|
+
console.log('class-hash output:', hashResult.combined);
|
|
310
|
+
classHash = extractClassHash(hashResult.combined);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
if (!classHash) {
|
|
314
|
+
console.error('❌ Could not extract class hash. Full declare output was:');
|
|
315
|
+
console.error(declareText);
|
|
316
|
+
process.exit(1);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
console.log(`✅ Class hash: ${classHash}`);
|
|
320
|
+
|
|
321
|
+
// ── 6. Deploy ─────────────────────────────────────────────────────────────
|
|
322
|
+
|
|
323
|
+
console.log(`\n🚀 Deploying contract instance on ${NETWORK} ...`);
|
|
324
|
+
|
|
325
|
+
const deploy = run(
|
|
326
|
+
`sncast --account ${accountName} \
|
|
327
|
+
deploy \
|
|
328
|
+
--class-hash ${classHash} \
|
|
329
|
+
--network ${NETWORK}`,
|
|
330
|
+
scarbProjectDir,
|
|
331
|
+
);
|
|
332
|
+
|
|
333
|
+
console.log('── sncast deploy stdout ───────────────────────────────────');
|
|
334
|
+
console.log(deploy.stdout || '(empty)');
|
|
335
|
+
console.log('── sncast deploy stderr ───────────────────────────────────');
|
|
336
|
+
console.log(deploy.stderr || '(empty)');
|
|
337
|
+
console.log('───────────────────────────────────────────────────────────');
|
|
338
|
+
|
|
339
|
+
if (deploy.status !== 0 && !deploy.combined.toLowerCase().includes('contract_address')) {
|
|
340
|
+
console.error('❌ sncast deploy failed. See output above.');
|
|
341
|
+
process.exit(1);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
const contractAddress = extractContractAddress(deploy.combined);
|
|
345
|
+
if (!contractAddress) {
|
|
346
|
+
console.error('❌ Deploy appeared to succeed but contract address could not be parsed.');
|
|
347
|
+
console.error(' Full output was printed above. Save the address manually if visible.');
|
|
348
|
+
process.exit(1);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
console.log(`✅ Contract deployed at: ${contractAddress}`);
|
|
352
|
+
|
|
353
|
+
// ── 7. Write deployment.json ──────────────────────────────────────────────
|
|
354
|
+
|
|
355
|
+
const deploymentInfo = {
|
|
356
|
+
contractAddress,
|
|
357
|
+
classHash,
|
|
358
|
+
abi,
|
|
359
|
+
circuitName,
|
|
360
|
+
network: NETWORK,
|
|
361
|
+
paths: {
|
|
362
|
+
verificationKey: path.join(absFolder, 'verification_key.json'),
|
|
363
|
+
zkey: path.join(absFolder, 'circuit_final.zkey'),
|
|
364
|
+
wasm: path.join(absFolder, `${circuitName}_js`, `${circuitName}.wasm`),
|
|
365
|
+
r1cs: path.join(absFolder, `${circuitName}.r1cs`),
|
|
366
|
+
},
|
|
367
|
+
deployedAt: new Date().toISOString(),
|
|
368
|
+
};
|
|
369
|
+
|
|
370
|
+
const deploymentJsonPath = path.join(absFolder, 'deployment.json');
|
|
371
|
+
fs.writeJsonSync(deploymentJsonPath, deploymentInfo, { spaces: 2 });
|
|
372
|
+
|
|
373
|
+
// ── 8. Summary ────────────────────────────────────────────────────────────
|
|
374
|
+
|
|
375
|
+
console.log('');
|
|
376
|
+
console.log('');
|
|
377
|
+
console.log(' ✅ Deployment complete!');
|
|
378
|
+
console.log(` Circuit : ${circuitName}`);
|
|
379
|
+
console.log(` Address : ${contractAddress}`);
|
|
380
|
+
console.log(` Network : ${NETWORK}`);
|
|
381
|
+
console.log(` Explorer : https://sepolia.starkscan.co/contract/${contractAddress}`);
|
|
382
|
+
console.log(` Saved to : ${deploymentJsonPath}`);
|
|
383
|
+
console.log('');
|
|
384
|
+
console.log('You can now call verifyProof() — it reads deployment.json automatically.');
|
|
385
|
+
|
|
386
|
+
} finally {
|
|
387
|
+
cleanupAccount(accountName);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
module.exports = { deployVerifier };
|
package/lib/test.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const { execSync } = require('child_process');
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
|
|
5
|
+
function testCircuit(folderPath, inputPath) {
|
|
6
|
+
try {
|
|
7
|
+
if (!fs.existsSync(folderPath)) {
|
|
8
|
+
console.error(`❌ Folder not found: ${folderPath}`);
|
|
9
|
+
process.exit(1);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
if (!fs.existsSync(inputPath)) {
|
|
13
|
+
console.error(`❌ input.json not found: ${inputPath}`);
|
|
14
|
+
process.exit(1);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const folderName = path.basename(folderPath);
|
|
18
|
+
const wasmDir = path.join(folderPath, `${folderName}_js`);
|
|
19
|
+
const wasmPath = path.join(wasmDir, `${folderName}.wasm`);
|
|
20
|
+
const zkeyPath = path.join(folderPath, 'circuit_final.zkey');
|
|
21
|
+
const proofPath = path.join(folderPath, 'proof.json');
|
|
22
|
+
const publicPath = path.join(folderPath, 'public.json');
|
|
23
|
+
|
|
24
|
+
if (!fs.existsSync(wasmPath)) {
|
|
25
|
+
console.error(`❌ .wasm file not found: ${wasmPath}`);
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (!fs.existsSync(zkeyPath)) {
|
|
30
|
+
console.error('❌ circuit_final.zkey not found. Make sure to compile first.');
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
console.log(`✅ Running groth16 fullprove...`);
|
|
35
|
+
execSync(`snarkjs groth16 fullprove ${inputPath} ${wasmPath} ${zkeyPath} ${proofPath} ${publicPath}`, {
|
|
36
|
+
stdio: 'inherit'
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
console.log(`✅ Proof generated at: ${proofPath}`);
|
|
40
|
+
console.log(`✅ Public inputs at: ${publicPath}`);
|
|
41
|
+
} catch (err) {
|
|
42
|
+
console.error('❌ Error generating proof:', err.message);
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
module.exports = { testCircuit };
|