create-izi-noir 0.1.1
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 +71 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +648 -0
- package/package.json +47 -0
package/README.md
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# create-izi-noir
|
|
2
|
+
|
|
3
|
+
CLI to scaffold IZI-NOIR ZK projects.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
# Create a new project interactively
|
|
9
|
+
npx create-izi-noir my-project
|
|
10
|
+
|
|
11
|
+
# Create with options
|
|
12
|
+
npx create-izi-noir my-project --template balance-proof --provider arkworks
|
|
13
|
+
|
|
14
|
+
# Skip prompts
|
|
15
|
+
npx create-izi-noir my-project --skip-install --skip-git
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Options
|
|
19
|
+
|
|
20
|
+
| Option | Description | Default |
|
|
21
|
+
|--------|-------------|---------|
|
|
22
|
+
| `-t, --template <template>` | Template to use (default, minimal, balance-proof) | default |
|
|
23
|
+
| `-p, --provider <provider>` | Proving provider (arkworks, barretenberg) | arkworks |
|
|
24
|
+
| `-y, --yes` | Skip prompts and use defaults | false |
|
|
25
|
+
| `--skip-install` | Skip npm install | false |
|
|
26
|
+
| `--skip-git` | Skip git initialization | false |
|
|
27
|
+
|
|
28
|
+
## Templates
|
|
29
|
+
|
|
30
|
+
### Default
|
|
31
|
+
Includes balance proof and age proof circuit examples.
|
|
32
|
+
|
|
33
|
+
### Minimal
|
|
34
|
+
Empty circuit for starting from scratch.
|
|
35
|
+
|
|
36
|
+
### Balance Proof
|
|
37
|
+
Single balance proof circuit example.
|
|
38
|
+
|
|
39
|
+
## Generated Project Structure
|
|
40
|
+
|
|
41
|
+
```
|
|
42
|
+
my-project/
|
|
43
|
+
├── circuits/
|
|
44
|
+
│ ├── balance-proof.ts # ZK circuit as JS function
|
|
45
|
+
│ ├── age-proof.ts
|
|
46
|
+
│ └── index.ts
|
|
47
|
+
├── generated/ # Compiled circuits (npm run build)
|
|
48
|
+
├── scripts/
|
|
49
|
+
│ └── test-proof.ts # Test script
|
|
50
|
+
├── package.json
|
|
51
|
+
├── tsconfig.json
|
|
52
|
+
├── izi-noir.config.ts
|
|
53
|
+
└── README.md
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Development
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
# Install dependencies
|
|
60
|
+
npm install
|
|
61
|
+
|
|
62
|
+
# Build the CLI
|
|
63
|
+
npm run build
|
|
64
|
+
|
|
65
|
+
# Test locally
|
|
66
|
+
node dist/index.js test-project
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## License
|
|
70
|
+
|
|
71
|
+
MIT
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,648 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/cli.ts
|
|
4
|
+
import { Command } from "commander";
|
|
5
|
+
|
|
6
|
+
// src/commands/init.ts
|
|
7
|
+
import path2 from "path";
|
|
8
|
+
import { execSync } from "child_process";
|
|
9
|
+
import pc3 from "picocolors";
|
|
10
|
+
|
|
11
|
+
// src/prompts/project.ts
|
|
12
|
+
import prompts from "prompts";
|
|
13
|
+
import pc from "picocolors";
|
|
14
|
+
var TEMPLATES = [
|
|
15
|
+
{ title: "Default (balance + age proofs)", value: "default" },
|
|
16
|
+
{ title: "Minimal (empty circuit)", value: "minimal" },
|
|
17
|
+
{ title: "Balance Proof only", value: "balance-proof" }
|
|
18
|
+
];
|
|
19
|
+
var PROVIDERS = [
|
|
20
|
+
{ title: "Arkworks (recommended)", value: "arkworks" },
|
|
21
|
+
{ title: "Barretenberg", value: "barretenberg" }
|
|
22
|
+
];
|
|
23
|
+
async function promptProjectOptions(defaults) {
|
|
24
|
+
console.log();
|
|
25
|
+
console.log(pc.bold(pc.cyan(" IZI-NOIR")) + " - Privacy-preserving toolkit for Solana");
|
|
26
|
+
console.log();
|
|
27
|
+
const questions = [];
|
|
28
|
+
if (!defaults.projectName) {
|
|
29
|
+
questions.push({
|
|
30
|
+
type: "text",
|
|
31
|
+
name: "projectName",
|
|
32
|
+
message: "Project name:",
|
|
33
|
+
initial: "my-zk-project",
|
|
34
|
+
validate: (value) => {
|
|
35
|
+
if (!value) return "Project name is required";
|
|
36
|
+
if (!/^[a-z0-9-_]+$/i.test(value)) {
|
|
37
|
+
return "Project name can only contain letters, numbers, hyphens, and underscores";
|
|
38
|
+
}
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
questions.push(
|
|
44
|
+
{
|
|
45
|
+
type: "select",
|
|
46
|
+
name: "template",
|
|
47
|
+
message: "Select a template:",
|
|
48
|
+
choices: TEMPLATES,
|
|
49
|
+
initial: TEMPLATES.findIndex((t) => t.value === defaults.template) || 0
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
type: "select",
|
|
53
|
+
name: "provider",
|
|
54
|
+
message: "Select proving provider:",
|
|
55
|
+
choices: PROVIDERS,
|
|
56
|
+
initial: PROVIDERS.findIndex((p) => p.value === defaults.provider) || 0
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
type: "confirm",
|
|
60
|
+
name: "installDeps",
|
|
61
|
+
message: "Install dependencies?",
|
|
62
|
+
initial: !defaults.skipInstall
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
type: "confirm",
|
|
66
|
+
name: "initGit",
|
|
67
|
+
message: "Initialize git repository?",
|
|
68
|
+
initial: !defaults.skipGit
|
|
69
|
+
}
|
|
70
|
+
);
|
|
71
|
+
try {
|
|
72
|
+
const response = await prompts(questions, {
|
|
73
|
+
onCancel: () => {
|
|
74
|
+
throw new Error("Operation cancelled");
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
return {
|
|
78
|
+
projectName: defaults.projectName || response.projectName,
|
|
79
|
+
template: response.template || defaults.template || "default",
|
|
80
|
+
provider: response.provider || defaults.provider || "arkworks",
|
|
81
|
+
skipInstall: response.installDeps === false,
|
|
82
|
+
skipGit: response.initGit === false
|
|
83
|
+
};
|
|
84
|
+
} catch {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// src/utils/fs.ts
|
|
90
|
+
import fs from "fs-extra";
|
|
91
|
+
import path from "path";
|
|
92
|
+
async function ensureDir(dir) {
|
|
93
|
+
await fs.ensureDir(dir);
|
|
94
|
+
}
|
|
95
|
+
async function writeFile(filePath, content) {
|
|
96
|
+
await fs.ensureDir(path.dirname(filePath));
|
|
97
|
+
await fs.writeFile(filePath, content, "utf-8");
|
|
98
|
+
}
|
|
99
|
+
async function directoryExists(dir) {
|
|
100
|
+
try {
|
|
101
|
+
const stat = await fs.stat(dir);
|
|
102
|
+
return stat.isDirectory();
|
|
103
|
+
} catch {
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
async function isDirectoryEmpty(dir) {
|
|
108
|
+
try {
|
|
109
|
+
const files = await fs.readdir(dir);
|
|
110
|
+
return files.length === 0;
|
|
111
|
+
} catch {
|
|
112
|
+
return true;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// src/utils/spinner.ts
|
|
117
|
+
import pc2 from "picocolors";
|
|
118
|
+
var frames = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
119
|
+
var Spinner = class {
|
|
120
|
+
message;
|
|
121
|
+
interval = null;
|
|
122
|
+
frameIndex = 0;
|
|
123
|
+
constructor(message) {
|
|
124
|
+
this.message = message;
|
|
125
|
+
}
|
|
126
|
+
start() {
|
|
127
|
+
process.stdout.write("\x1B[?25l");
|
|
128
|
+
this.interval = setInterval(() => {
|
|
129
|
+
const frame = pc2.cyan(frames[this.frameIndex]);
|
|
130
|
+
process.stdout.write(`\r${frame} ${this.message}`);
|
|
131
|
+
this.frameIndex = (this.frameIndex + 1) % frames.length;
|
|
132
|
+
}, 80);
|
|
133
|
+
}
|
|
134
|
+
stop(success = true) {
|
|
135
|
+
if (this.interval) {
|
|
136
|
+
clearInterval(this.interval);
|
|
137
|
+
this.interval = null;
|
|
138
|
+
}
|
|
139
|
+
process.stdout.write("\x1B[?25h");
|
|
140
|
+
const icon = success ? pc2.green("\u2713") : pc2.red("\u2717");
|
|
141
|
+
process.stdout.write(`\r${icon} ${this.message}
|
|
142
|
+
`);
|
|
143
|
+
}
|
|
144
|
+
update(message) {
|
|
145
|
+
this.message = message;
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
function createSpinner(message) {
|
|
149
|
+
return new Spinner(message);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// src/generators/package-json.ts
|
|
153
|
+
function generatePackageJson(options) {
|
|
154
|
+
const pkg = {
|
|
155
|
+
name: options.projectName,
|
|
156
|
+
version: "0.1.0",
|
|
157
|
+
description: "ZK circuits built with IZI-NOIR",
|
|
158
|
+
type: "module",
|
|
159
|
+
main: "./dist/index.js",
|
|
160
|
+
types: "./dist/index.d.ts",
|
|
161
|
+
exports: {
|
|
162
|
+
".": {
|
|
163
|
+
types: "./dist/index.d.ts",
|
|
164
|
+
import: "./dist/index.js"
|
|
165
|
+
},
|
|
166
|
+
"./circuits": {
|
|
167
|
+
types: "./circuits/index.d.ts",
|
|
168
|
+
import: "./circuits/index.js"
|
|
169
|
+
}
|
|
170
|
+
},
|
|
171
|
+
files: ["dist", "circuits", "generated"],
|
|
172
|
+
scripts: {
|
|
173
|
+
build: "izi-noir build && tsc",
|
|
174
|
+
"build:circuits": "izi-noir build",
|
|
175
|
+
dev: "izi-noir build --watch",
|
|
176
|
+
test: "tsx scripts/test-proof.ts",
|
|
177
|
+
prepublishOnly: "npm run build"
|
|
178
|
+
},
|
|
179
|
+
dependencies: {
|
|
180
|
+
"@izi-noir/sdk": "^0.1.0"
|
|
181
|
+
},
|
|
182
|
+
devDependencies: {
|
|
183
|
+
"@types/node": "^22.0.0",
|
|
184
|
+
tsx: "^4.0.0",
|
|
185
|
+
typescript: "^5.4.0"
|
|
186
|
+
},
|
|
187
|
+
keywords: ["zk", "noir", "zero-knowledge", "privacy", "solana"],
|
|
188
|
+
license: "MIT"
|
|
189
|
+
};
|
|
190
|
+
return JSON.stringify(pkg, null, 2);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// src/generators/tsconfig.ts
|
|
194
|
+
function generateTsconfig() {
|
|
195
|
+
const config = {
|
|
196
|
+
compilerOptions: {
|
|
197
|
+
target: "ES2022",
|
|
198
|
+
module: "ESNext",
|
|
199
|
+
moduleResolution: "bundler",
|
|
200
|
+
lib: ["ES2022"],
|
|
201
|
+
outDir: "./dist",
|
|
202
|
+
rootDir: ".",
|
|
203
|
+
strict: true,
|
|
204
|
+
esModuleInterop: true,
|
|
205
|
+
skipLibCheck: true,
|
|
206
|
+
forceConsistentCasingInFileNames: true,
|
|
207
|
+
declaration: true,
|
|
208
|
+
declarationMap: true,
|
|
209
|
+
sourceMap: true,
|
|
210
|
+
resolveJsonModule: true
|
|
211
|
+
},
|
|
212
|
+
include: ["circuits/**/*", "generated/**/*", "scripts/**/*"],
|
|
213
|
+
exclude: ["node_modules", "dist"]
|
|
214
|
+
};
|
|
215
|
+
return JSON.stringify(config, null, 2);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// src/generators/config.ts
|
|
219
|
+
function generateConfig(options) {
|
|
220
|
+
return `import { defineConfig } from '@izi-noir/sdk';
|
|
221
|
+
|
|
222
|
+
export default defineConfig({
|
|
223
|
+
// Directory containing your circuit files
|
|
224
|
+
circuitsDir: './circuits',
|
|
225
|
+
|
|
226
|
+
// Output directory for compiled circuits
|
|
227
|
+
outDir: './generated',
|
|
228
|
+
|
|
229
|
+
// Proving provider to use
|
|
230
|
+
provider: '${options.provider}',
|
|
231
|
+
|
|
232
|
+
// Enable watch mode optimizations
|
|
233
|
+
watch: {
|
|
234
|
+
// Debounce file changes (ms)
|
|
235
|
+
debounce: 100,
|
|
236
|
+
},
|
|
237
|
+
});
|
|
238
|
+
`;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// src/generators/circuits.ts
|
|
242
|
+
function generateBalanceProof() {
|
|
243
|
+
return `/**
|
|
244
|
+
* Balance Proof Circuit
|
|
245
|
+
*
|
|
246
|
+
* Proves that a private balance is greater than or equal to a public threshold
|
|
247
|
+
* without revealing the actual balance.
|
|
248
|
+
*
|
|
249
|
+
* @param threshold - The minimum required balance (public)
|
|
250
|
+
* @param balance - The actual balance to prove (private, not revealed)
|
|
251
|
+
*/
|
|
252
|
+
export function balanceProof(
|
|
253
|
+
[threshold]: [number],
|
|
254
|
+
[balance]: [number]
|
|
255
|
+
): void {
|
|
256
|
+
assert(balance >= threshold);
|
|
257
|
+
}
|
|
258
|
+
`;
|
|
259
|
+
}
|
|
260
|
+
function generateAgeProof() {
|
|
261
|
+
return `/**
|
|
262
|
+
* Age Proof Circuit
|
|
263
|
+
*
|
|
264
|
+
* Proves that a private birth year results in an age >= minimum age
|
|
265
|
+
* without revealing the actual birth year.
|
|
266
|
+
*
|
|
267
|
+
* @param currentYear - The current year (public)
|
|
268
|
+
* @param minAge - The minimum required age (public)
|
|
269
|
+
* @param birthYear - The actual birth year (private, not revealed)
|
|
270
|
+
*/
|
|
271
|
+
export function ageProof(
|
|
272
|
+
[currentYear, minAge]: [number, number],
|
|
273
|
+
[birthYear]: [number]
|
|
274
|
+
): void {
|
|
275
|
+
const age = currentYear - birthYear;
|
|
276
|
+
assert(age >= minAge);
|
|
277
|
+
}
|
|
278
|
+
`;
|
|
279
|
+
}
|
|
280
|
+
function generateMinimalCircuit() {
|
|
281
|
+
return `/**
|
|
282
|
+
* My Custom Circuit
|
|
283
|
+
*
|
|
284
|
+
* Replace this with your own circuit logic.
|
|
285
|
+
* Use assert() statements to define constraints.
|
|
286
|
+
*
|
|
287
|
+
* @param publicInput - A public input value
|
|
288
|
+
* @param privateInput - A private input value (not revealed)
|
|
289
|
+
*/
|
|
290
|
+
export function myCircuit(
|
|
291
|
+
[publicInput]: [number],
|
|
292
|
+
[privateInput]: [number]
|
|
293
|
+
): void {
|
|
294
|
+
// Example: prove that private input equals public input
|
|
295
|
+
assert(privateInput === publicInput);
|
|
296
|
+
}
|
|
297
|
+
`;
|
|
298
|
+
}
|
|
299
|
+
function generateCircuitsIndex(template) {
|
|
300
|
+
switch (template) {
|
|
301
|
+
case "minimal":
|
|
302
|
+
return `export { myCircuit } from './my-circuit.js';
|
|
303
|
+
`;
|
|
304
|
+
case "balance-proof":
|
|
305
|
+
return `export { balanceProof } from './balance-proof.js';
|
|
306
|
+
`;
|
|
307
|
+
default:
|
|
308
|
+
return `export { balanceProof } from './balance-proof.js';
|
|
309
|
+
export { ageProof } from './age-proof.js';
|
|
310
|
+
`;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// src/generators/scripts.ts
|
|
315
|
+
function generateTestScript(options) {
|
|
316
|
+
const imports = getImports(options.template);
|
|
317
|
+
const tests = getTests(options.template);
|
|
318
|
+
return `/**
|
|
319
|
+
* Test script for ZK proofs
|
|
320
|
+
*
|
|
321
|
+
* Run with: npm test
|
|
322
|
+
*/
|
|
323
|
+
import { IziNoir, Provider } from '@izi-noir/sdk';
|
|
324
|
+
${imports}
|
|
325
|
+
|
|
326
|
+
async function main() {
|
|
327
|
+
console.log('Initializing IZI-NOIR...');
|
|
328
|
+
const izi = await IziNoir.init({
|
|
329
|
+
provider: Provider.${capitalizeFirst(options.provider)},
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
${tests}
|
|
333
|
+
|
|
334
|
+
console.log('\\n\u2713 All proofs verified successfully!');
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
main().catch((error) => {
|
|
338
|
+
console.error('Error:', error);
|
|
339
|
+
process.exit(1);
|
|
340
|
+
});
|
|
341
|
+
`;
|
|
342
|
+
}
|
|
343
|
+
function getImports(template) {
|
|
344
|
+
switch (template) {
|
|
345
|
+
case "minimal":
|
|
346
|
+
return `import { myCircuit } from '../circuits/index.js';`;
|
|
347
|
+
case "balance-proof":
|
|
348
|
+
return `import { balanceProof } from '../circuits/index.js';`;
|
|
349
|
+
default:
|
|
350
|
+
return `import { balanceProof, ageProof } from '../circuits/index.js';`;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
function getTests(template) {
|
|
354
|
+
switch (template) {
|
|
355
|
+
case "minimal":
|
|
356
|
+
return ` // Test: myCircuit
|
|
357
|
+
console.log('\\nTesting myCircuit...');
|
|
358
|
+
const result1 = await izi.createProof(
|
|
359
|
+
myCircuit,
|
|
360
|
+
[42], // public: expected value
|
|
361
|
+
[42] // private: actual value
|
|
362
|
+
);
|
|
363
|
+
console.log(' Proof verified:', result1.verified);`;
|
|
364
|
+
case "balance-proof":
|
|
365
|
+
return ` // Test: Balance Proof
|
|
366
|
+
console.log('\\nTesting balanceProof...');
|
|
367
|
+
const result1 = await izi.createProof(
|
|
368
|
+
balanceProof,
|
|
369
|
+
[100], // public: threshold
|
|
370
|
+
[1500] // private: actual balance
|
|
371
|
+
);
|
|
372
|
+
console.log(' Proof verified:', result1.verified);
|
|
373
|
+
console.log(' The prover has >= 100 balance (actual: hidden)');`;
|
|
374
|
+
default:
|
|
375
|
+
return ` // Test 1: Balance Proof
|
|
376
|
+
console.log('\\nTesting balanceProof...');
|
|
377
|
+
const result1 = await izi.createProof(
|
|
378
|
+
balanceProof,
|
|
379
|
+
[100], // public: threshold
|
|
380
|
+
[1500] // private: actual balance
|
|
381
|
+
);
|
|
382
|
+
console.log(' Proof verified:', result1.verified);
|
|
383
|
+
console.log(' The prover has >= 100 balance (actual: hidden)');
|
|
384
|
+
|
|
385
|
+
// Test 2: Age Proof
|
|
386
|
+
console.log('\\nTesting ageProof...');
|
|
387
|
+
const result2 = await izi.createProof(
|
|
388
|
+
ageProof,
|
|
389
|
+
[2024, 18], // public: current year, minimum age
|
|
390
|
+
[1990] // private: birth year
|
|
391
|
+
);
|
|
392
|
+
console.log(' Proof verified:', result2.verified);
|
|
393
|
+
console.log(' The prover is >= 18 years old (birth year: hidden)');`;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
function capitalizeFirst(str) {
|
|
397
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// src/generators/readme.ts
|
|
401
|
+
function generateReadme(options) {
|
|
402
|
+
return `# ${options.projectName}
|
|
403
|
+
|
|
404
|
+
ZK circuits built with [IZI-NOIR](https://github.com/izi-noir/izi-noir).
|
|
405
|
+
|
|
406
|
+
## Getting Started
|
|
407
|
+
|
|
408
|
+
\`\`\`bash
|
|
409
|
+
# Build circuits
|
|
410
|
+
npm run build
|
|
411
|
+
|
|
412
|
+
# Run tests
|
|
413
|
+
npm test
|
|
414
|
+
|
|
415
|
+
# Watch mode (rebuild on changes)
|
|
416
|
+
npm run dev
|
|
417
|
+
\`\`\`
|
|
418
|
+
|
|
419
|
+
## Project Structure
|
|
420
|
+
|
|
421
|
+
\`\`\`
|
|
422
|
+
${options.projectName}/
|
|
423
|
+
\u251C\u2500\u2500 circuits/ # Your ZK circuit definitions
|
|
424
|
+
\u2502 \u251C\u2500\u2500 *.ts # Circuits as JS functions with assert()
|
|
425
|
+
\u2502 \u2514\u2500\u2500 index.ts # Re-exports
|
|
426
|
+
\u251C\u2500\u2500 generated/ # Compiled circuits (auto-generated)
|
|
427
|
+
\u2502 \u251C\u2500\u2500 *.json # Compiled circuit artifacts
|
|
428
|
+
\u2502 \u2514\u2500\u2500 index.ts # Typed re-exports
|
|
429
|
+
\u251C\u2500\u2500 scripts/
|
|
430
|
+
\u2502 \u2514\u2500\u2500 test-proof.ts # Local test script
|
|
431
|
+
\u251C\u2500\u2500 izi-noir.config.ts # Build configuration
|
|
432
|
+
\u2514\u2500\u2500 package.json
|
|
433
|
+
\`\`\`
|
|
434
|
+
|
|
435
|
+
## Writing Circuits
|
|
436
|
+
|
|
437
|
+
Circuits are JavaScript functions with \`assert()\` statements:
|
|
438
|
+
|
|
439
|
+
\`\`\`typescript
|
|
440
|
+
// circuits/my-circuit.ts
|
|
441
|
+
export function myCircuit(
|
|
442
|
+
[publicInput]: [number],
|
|
443
|
+
[privateInput]: [number]
|
|
444
|
+
): void {
|
|
445
|
+
assert(privateInput >= publicInput);
|
|
446
|
+
}
|
|
447
|
+
\`\`\`
|
|
448
|
+
|
|
449
|
+
## Usage in Frontend
|
|
450
|
+
|
|
451
|
+
\`\`\`typescript
|
|
452
|
+
import { IziNoir, Provider } from '@izi-noir/sdk';
|
|
453
|
+
import { myCircuit } from '${options.projectName}/circuits';
|
|
454
|
+
|
|
455
|
+
const izi = await IziNoir.init({ provider: Provider.Arkworks });
|
|
456
|
+
|
|
457
|
+
const proof = await izi.createProof(
|
|
458
|
+
myCircuit,
|
|
459
|
+
[100], // public inputs
|
|
460
|
+
[1500] // private inputs (hidden)
|
|
461
|
+
);
|
|
462
|
+
|
|
463
|
+
console.log(proof.verified); // true
|
|
464
|
+
\`\`\`
|
|
465
|
+
|
|
466
|
+
## Publishing
|
|
467
|
+
|
|
468
|
+
\`\`\`bash
|
|
469
|
+
npm publish
|
|
470
|
+
\`\`\`
|
|
471
|
+
|
|
472
|
+
Your circuits can then be installed as a dependency in other projects.
|
|
473
|
+
|
|
474
|
+
## Learn More
|
|
475
|
+
|
|
476
|
+
- [IZI-NOIR Documentation](https://github.com/izi-noir/izi-noir)
|
|
477
|
+
- [Noir Language](https://noir-lang.org)
|
|
478
|
+
`;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
// src/generators/gitignore.ts
|
|
482
|
+
function generateGitignore() {
|
|
483
|
+
return `# Dependencies
|
|
484
|
+
node_modules/
|
|
485
|
+
|
|
486
|
+
# Build output
|
|
487
|
+
dist/
|
|
488
|
+
generated/
|
|
489
|
+
|
|
490
|
+
# IDE
|
|
491
|
+
.idea/
|
|
492
|
+
.vscode/
|
|
493
|
+
*.swp
|
|
494
|
+
*.swo
|
|
495
|
+
|
|
496
|
+
# OS
|
|
497
|
+
.DS_Store
|
|
498
|
+
Thumbs.db
|
|
499
|
+
|
|
500
|
+
# Logs
|
|
501
|
+
*.log
|
|
502
|
+
npm-debug.log*
|
|
503
|
+
|
|
504
|
+
# Environment
|
|
505
|
+
.env
|
|
506
|
+
.env.local
|
|
507
|
+
.env.*.local
|
|
508
|
+
|
|
509
|
+
# Test coverage
|
|
510
|
+
coverage/
|
|
511
|
+
`;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
// src/commands/init.ts
|
|
515
|
+
async function initCommand(projectName, options) {
|
|
516
|
+
let projectOptions;
|
|
517
|
+
if (options.yes && projectName) {
|
|
518
|
+
projectOptions = {
|
|
519
|
+
projectName,
|
|
520
|
+
template: options.template,
|
|
521
|
+
provider: options.provider,
|
|
522
|
+
skipInstall: options.skipInstall,
|
|
523
|
+
skipGit: options.skipGit
|
|
524
|
+
};
|
|
525
|
+
} else {
|
|
526
|
+
projectOptions = await promptProjectOptions({
|
|
527
|
+
projectName,
|
|
528
|
+
template: options.template,
|
|
529
|
+
provider: options.provider,
|
|
530
|
+
skipInstall: options.skipInstall,
|
|
531
|
+
skipGit: options.skipGit
|
|
532
|
+
});
|
|
533
|
+
}
|
|
534
|
+
if (!projectOptions) {
|
|
535
|
+
console.log(pc3.yellow("\nOperation cancelled."));
|
|
536
|
+
process.exit(0);
|
|
537
|
+
}
|
|
538
|
+
const projectDir = path2.resolve(process.cwd(), projectOptions.projectName);
|
|
539
|
+
if (await directoryExists(projectDir)) {
|
|
540
|
+
if (!await isDirectoryEmpty(projectDir)) {
|
|
541
|
+
console.log(
|
|
542
|
+
pc3.red(`
|
|
543
|
+
Error: Directory "${projectOptions.projectName}" already exists and is not empty.`)
|
|
544
|
+
);
|
|
545
|
+
process.exit(1);
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
console.log();
|
|
549
|
+
const spinner = createSpinner("Creating project structure...");
|
|
550
|
+
spinner.start();
|
|
551
|
+
try {
|
|
552
|
+
await createProjectStructure(projectDir, projectOptions);
|
|
553
|
+
spinner.stop(true);
|
|
554
|
+
} catch (error) {
|
|
555
|
+
spinner.stop(false);
|
|
556
|
+
console.error(pc3.red("\nFailed to create project structure:"), error);
|
|
557
|
+
process.exit(1);
|
|
558
|
+
}
|
|
559
|
+
if (!projectOptions.skipGit) {
|
|
560
|
+
const gitSpinner = createSpinner("Initializing git repository...");
|
|
561
|
+
gitSpinner.start();
|
|
562
|
+
try {
|
|
563
|
+
execSync("git init", { cwd: projectDir, stdio: "ignore" });
|
|
564
|
+
gitSpinner.stop(true);
|
|
565
|
+
} catch {
|
|
566
|
+
gitSpinner.stop(false);
|
|
567
|
+
console.log(pc3.yellow(" Warning: Failed to initialize git repository"));
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
if (!projectOptions.skipInstall) {
|
|
571
|
+
const installSpinner = createSpinner("Installing dependencies...");
|
|
572
|
+
installSpinner.start();
|
|
573
|
+
try {
|
|
574
|
+
execSync("npm install", { cwd: projectDir, stdio: "ignore" });
|
|
575
|
+
installSpinner.stop(true);
|
|
576
|
+
} catch {
|
|
577
|
+
installSpinner.stop(false);
|
|
578
|
+
console.log(pc3.yellow(' Warning: Failed to install dependencies. Run "npm install" manually.'));
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
printSuccessMessage(projectOptions);
|
|
582
|
+
}
|
|
583
|
+
async function createProjectStructure(projectDir, options) {
|
|
584
|
+
await ensureDir(path2.join(projectDir, "circuits"));
|
|
585
|
+
await ensureDir(path2.join(projectDir, "generated"));
|
|
586
|
+
await ensureDir(path2.join(projectDir, "scripts"));
|
|
587
|
+
const files = [
|
|
588
|
+
["package.json", generatePackageJson(options)],
|
|
589
|
+
["tsconfig.json", generateTsconfig()],
|
|
590
|
+
["izi-noir.config.ts", generateConfig(options)],
|
|
591
|
+
["README.md", generateReadme(options)],
|
|
592
|
+
[".gitignore", generateGitignore()],
|
|
593
|
+
["scripts/test-proof.ts", generateTestScript(options)]
|
|
594
|
+
];
|
|
595
|
+
switch (options.template) {
|
|
596
|
+
case "minimal":
|
|
597
|
+
files.push(["circuits/my-circuit.ts", generateMinimalCircuit()]);
|
|
598
|
+
break;
|
|
599
|
+
case "balance-proof":
|
|
600
|
+
files.push(["circuits/balance-proof.ts", generateBalanceProof()]);
|
|
601
|
+
break;
|
|
602
|
+
default:
|
|
603
|
+
files.push(["circuits/balance-proof.ts", generateBalanceProof()]);
|
|
604
|
+
files.push(["circuits/age-proof.ts", generateAgeProof()]);
|
|
605
|
+
break;
|
|
606
|
+
}
|
|
607
|
+
files.push(["circuits/index.ts", generateCircuitsIndex(options.template)]);
|
|
608
|
+
files.push([
|
|
609
|
+
"generated/.gitkeep",
|
|
610
|
+
"# This directory contains compiled circuit artifacts\n# Generated by: npm run build\n"
|
|
611
|
+
]);
|
|
612
|
+
await Promise.all(
|
|
613
|
+
files.map(
|
|
614
|
+
([relativePath, content]) => writeFile(path2.join(projectDir, relativePath), content)
|
|
615
|
+
)
|
|
616
|
+
);
|
|
617
|
+
}
|
|
618
|
+
function printSuccessMessage(options) {
|
|
619
|
+
console.log();
|
|
620
|
+
console.log(pc3.green("\u2713") + " Project created successfully!");
|
|
621
|
+
console.log();
|
|
622
|
+
console.log("Next steps:");
|
|
623
|
+
console.log();
|
|
624
|
+
console.log(pc3.cyan(` cd ${options.projectName}`));
|
|
625
|
+
if (options.skipInstall) {
|
|
626
|
+
console.log(pc3.cyan(" npm install"));
|
|
627
|
+
}
|
|
628
|
+
console.log(pc3.cyan(" npm run build"));
|
|
629
|
+
console.log(pc3.cyan(" npm test"));
|
|
630
|
+
console.log();
|
|
631
|
+
console.log("To start developing:");
|
|
632
|
+
console.log();
|
|
633
|
+
console.log(pc3.dim(" 1. Edit circuits in circuits/*.ts"));
|
|
634
|
+
console.log(pc3.dim(' 2. Run "npm run dev" for watch mode'));
|
|
635
|
+
console.log(pc3.dim(' 3. Test with "npm test"'));
|
|
636
|
+
console.log();
|
|
637
|
+
console.log(
|
|
638
|
+
pc3.dim("Learn more: ") + pc3.blue("https://github.com/izi-noir/izi-noir")
|
|
639
|
+
);
|
|
640
|
+
console.log();
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
// src/cli.ts
|
|
644
|
+
var VERSION = "0.1.0";
|
|
645
|
+
var cli = new Command().name("create-izi-noir").description("Create a new IZI-NOIR ZK project").version(VERSION).argument("[project-name]", "Name of the project to create").option("-t, --template <template>", "Template to use", "default").option("-p, --provider <provider>", "Proving provider", "arkworks").option("-y, --yes", "Skip prompts and use defaults", false).option("--skip-install", "Skip npm install", false).option("--skip-git", "Skip git initialization", false).action(initCommand);
|
|
646
|
+
|
|
647
|
+
// src/index.ts
|
|
648
|
+
cli.parse(process.argv);
|
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "create-izi-noir",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "CLI to scaffold IZI-NOIR ZK projects",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"create-izi-noir": "./dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"main": "./dist/index.js",
|
|
10
|
+
"files": [
|
|
11
|
+
"dist",
|
|
12
|
+
"templates"
|
|
13
|
+
],
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build": "tsup src/index.ts --format esm --dts --clean",
|
|
16
|
+
"dev": "tsup src/index.ts --format esm --watch",
|
|
17
|
+
"prepublishOnly": "npm run build"
|
|
18
|
+
},
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"commander": "^12.0.0",
|
|
21
|
+
"prompts": "^2.4.2",
|
|
22
|
+
"picocolors": "^1.0.0",
|
|
23
|
+
"fs-extra": "^11.0.0"
|
|
24
|
+
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"@types/fs-extra": "^11.0.4",
|
|
27
|
+
"@types/node": "^22.0.0",
|
|
28
|
+
"@types/prompts": "^2.4.9",
|
|
29
|
+
"tsup": "^8.0.0",
|
|
30
|
+
"typescript": "^5.4.0"
|
|
31
|
+
},
|
|
32
|
+
"keywords": [
|
|
33
|
+
"zk",
|
|
34
|
+
"noir",
|
|
35
|
+
"solana",
|
|
36
|
+
"privacy",
|
|
37
|
+
"zero-knowledge",
|
|
38
|
+
"scaffold",
|
|
39
|
+
"cli"
|
|
40
|
+
],
|
|
41
|
+
"license": "MIT",
|
|
42
|
+
"repository": {
|
|
43
|
+
"type": "git",
|
|
44
|
+
"url": "https://github.com/izi-noir/izi-noir.git",
|
|
45
|
+
"directory": "packages/create-izi-noir"
|
|
46
|
+
}
|
|
47
|
+
}
|