zk-credits 0.1.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/circuits/manifest.json +16 -0
- package/circuits/rln_nullifier.wasm +0 -0
- package/circuits/rln_nullifier_final.zkey +0 -0
- package/circuits/verification_key_rln.json +109 -0
- package/dist/artifact-manifest.d.ts +8 -0
- package/dist/artifact-manifest.js +29 -0
- package/dist/cli-runtime.d.ts +15 -0
- package/dist/cli-runtime.js +55 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +175 -0
- package/dist/codex-launcher.d.ts +10 -0
- package/dist/codex-launcher.js +17 -0
- package/dist/codex-profile.d.ts +16 -0
- package/dist/codex-profile.js +103 -0
- package/dist/identity.d.ts +18 -0
- package/dist/identity.js +48 -0
- package/dist/local-prover.d.ts +18 -0
- package/dist/local-prover.js +32 -0
- package/dist/membership-client.d.ts +14 -0
- package/dist/membership-client.js +66 -0
- package/dist/node-sidecar-lifecycle.d.ts +21 -0
- package/dist/node-sidecar-lifecycle.js +65 -0
- package/dist/server-startup.d.ts +6 -0
- package/dist/server-startup.js +6 -0
- package/dist/sidecar-config.d.ts +9 -0
- package/dist/sidecar-config.js +23 -0
- package/dist/sidecar-lifecycle.d.ts +13 -0
- package/dist/sidecar-lifecycle.js +14 -0
- package/dist/sidecar-state.d.ts +2 -0
- package/dist/sidecar-state.js +18 -0
- package/dist/sidecar.d.ts +27 -0
- package/dist/sidecar.js +153 -0
- package/dist/ticket-ledger.d.ts +22 -0
- package/dist/ticket-ledger.js +91 -0
- package/dist/zk-credits.js +1087 -0
- package/package.json +36 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"artifacts": [
|
|
3
|
+
{
|
|
4
|
+
"file": "rln_nullifier.wasm",
|
|
5
|
+
"sha256": "38370baebbafb76562d5c94e15354c04152c388bffe1bf85faeeb4dd221538c1"
|
|
6
|
+
},
|
|
7
|
+
{
|
|
8
|
+
"file": "rln_nullifier_final.zkey",
|
|
9
|
+
"sha256": "11cd9e881437283701dcebd75be469d57ec7428de3495d229268eb75f2413cc8"
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"file": "verification_key_rln.json",
|
|
13
|
+
"sha256": "9a521e0e19dc272c281f1a41abf79571bb0094cda653c890bdd95aa5b4c5f1ee"
|
|
14
|
+
}
|
|
15
|
+
]
|
|
16
|
+
}
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
{
|
|
2
|
+
"protocol": "groth16",
|
|
3
|
+
"curve": "bls12381",
|
|
4
|
+
"nPublic": 4,
|
|
5
|
+
"vk_alpha_1": [
|
|
6
|
+
"1244916799719111115985824406146514200983043647072123709622583920496525002856764215221448931962172516669612006664011",
|
|
7
|
+
"674184716755852549412886332979972587855277846527799005000484605021468038416766675025743003205822498453235045691476",
|
|
8
|
+
"1"
|
|
9
|
+
],
|
|
10
|
+
"vk_beta_2": [
|
|
11
|
+
[
|
|
12
|
+
"2455251836070541788489529793762142346199398025992507018540619039036086814384951975176756650000846004361344070161304",
|
|
13
|
+
"2937824279562491013596404255358969138585343050874888399768103071899950746606483895876569409026151415551079740037761"
|
|
14
|
+
],
|
|
15
|
+
[
|
|
16
|
+
"446083790294330119881832403473761928440559063068309621037545623082473144505427418093982114100409901098317111775821",
|
|
17
|
+
"3785055939750926364236474492083690801000055773112847122159407366123504713227708230770664301332755836784284502908712"
|
|
18
|
+
],
|
|
19
|
+
[
|
|
20
|
+
"1",
|
|
21
|
+
"0"
|
|
22
|
+
]
|
|
23
|
+
],
|
|
24
|
+
"vk_gamma_2": [
|
|
25
|
+
[
|
|
26
|
+
"352701069587466618187139116011060144890029952792775240219908644239793785735715026873347600343865175952761926303160",
|
|
27
|
+
"3059144344244213709971259814753781636986470325476647558659373206291635324768958432433509563104347017837885763365758"
|
|
28
|
+
],
|
|
29
|
+
[
|
|
30
|
+
"1985150602287291935568054521177171638300868978215655730859378665066344726373823718423869104263333984641494340347905",
|
|
31
|
+
"927553665492332455747201965776037880757740193453592970025027978793976877002675564980949289727957565575433344219582"
|
|
32
|
+
],
|
|
33
|
+
[
|
|
34
|
+
"1",
|
|
35
|
+
"0"
|
|
36
|
+
]
|
|
37
|
+
],
|
|
38
|
+
"vk_delta_2": [
|
|
39
|
+
[
|
|
40
|
+
"815903917344004787967184712590545096236532819656388622025249611089881028477817865557111876667504441704194684680307",
|
|
41
|
+
"670418350436737966704976106638632654750417626321542221556129023573394146719830978038172500968777166806783233326358"
|
|
42
|
+
],
|
|
43
|
+
[
|
|
44
|
+
"132242538892870542023564177985854902449332198072826003671372574646963034098929452508380735176272713149086113864544",
|
|
45
|
+
"1295474109123148911958332787357286834151182410719136962183460732527817478371821937388205172048254642287772006009264"
|
|
46
|
+
],
|
|
47
|
+
[
|
|
48
|
+
"1",
|
|
49
|
+
"0"
|
|
50
|
+
]
|
|
51
|
+
],
|
|
52
|
+
"vk_alphabeta_12": [
|
|
53
|
+
[
|
|
54
|
+
[
|
|
55
|
+
"2148218340318415560785282432131639593244917611573725139848778170535434244212026178066037788310472564349970250282392",
|
|
56
|
+
"3886099966455474325354315497186064113709942495020874416427588084988203169653116890509783720642066036079024288318367"
|
|
57
|
+
],
|
|
58
|
+
[
|
|
59
|
+
"2147733873846352773264547871382924081657868204207859655073999707937942259125041863315023713461664906836183909349427",
|
|
60
|
+
"3300509236653870608433795081198008200257706137874605155741973379043673218059121533531885971135301481641148990288908"
|
|
61
|
+
],
|
|
62
|
+
[
|
|
63
|
+
"1457040979867014527178278917296122843565121729489643303266593330575329991280167715824060993604767796096686114833558",
|
|
64
|
+
"522202240083044464005146299554446710117064086086358260623118384301631920417346027196182771281895994950369033642655"
|
|
65
|
+
]
|
|
66
|
+
],
|
|
67
|
+
[
|
|
68
|
+
[
|
|
69
|
+
"2143490186396385444016488966037949777514548881502140436891739250424332358613767373363354273246538302735780964291134",
|
|
70
|
+
"992359013426272838367672158158652816958223156384535853564383892165623437873615951263884124498691885172712210460870"
|
|
71
|
+
],
|
|
72
|
+
[
|
|
73
|
+
"2930464239864002107991444247101178291833963173368894752425555503421946405791467731459004456083402426838355238725274",
|
|
74
|
+
"1641122466550679068287562812263191357777988622153078979652004605642842424522164600037157683230157584394685025158128"
|
|
75
|
+
],
|
|
76
|
+
[
|
|
77
|
+
"3713193058400064755185876715564152409762918217604594177751650270255812088538785420914214301101967016069607286158727",
|
|
78
|
+
"3330823746424537451818547927613235469316182723960266221891897980402198103295396404876595659434703874739812673040531"
|
|
79
|
+
]
|
|
80
|
+
]
|
|
81
|
+
],
|
|
82
|
+
"IC": [
|
|
83
|
+
[
|
|
84
|
+
"2619981925219799044925292874804472181322403305042960540267007572554148292764967234794422060297405328360757510836410",
|
|
85
|
+
"1671878666008695368264725089725176391458488012496484085432493950640398385781945148513326456164087180642228352857154",
|
|
86
|
+
"1"
|
|
87
|
+
],
|
|
88
|
+
[
|
|
89
|
+
"2962167839060010823612043197241393772530852323612584445859764903692967795672028163825735615820801435857580681916666",
|
|
90
|
+
"149941004408393200420344522245393435122492041086792788867727881494885562834027422806934271879564707675908925017073",
|
|
91
|
+
"1"
|
|
92
|
+
],
|
|
93
|
+
[
|
|
94
|
+
"2827687415023160826278720364233374930905679736186024874228420390555381619909205688587633404816747958873736490833250",
|
|
95
|
+
"1158462108537411797695075524175255149628850521698164664688681863659381326196535473842851262539366227662015891009304",
|
|
96
|
+
"1"
|
|
97
|
+
],
|
|
98
|
+
[
|
|
99
|
+
"529262520578754285118871536040905446947891826400031106172291657766477328537381420544111896054250568733141121144894",
|
|
100
|
+
"76786170645685285882188204133787783442160455050559729010717405837827594403467807550443285025529284869053889709362",
|
|
101
|
+
"1"
|
|
102
|
+
],
|
|
103
|
+
[
|
|
104
|
+
"532817504610992030868130360294801571860447285842057982753015098208520483232734639607596289339602237219420675894443",
|
|
105
|
+
"3537860490759685255700833399282272211830248347533426758099162862063308826090393262829666104619743494237271770797411",
|
|
106
|
+
"1"
|
|
107
|
+
]
|
|
108
|
+
]
|
|
109
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export interface CircuitArtifact {
|
|
2
|
+
file: string;
|
|
3
|
+
sha256: string;
|
|
4
|
+
}
|
|
5
|
+
/** Version-pinned contents of the circuits directory shipped in this package. */
|
|
6
|
+
export declare const CIRCUIT_MANIFEST: readonly CircuitArtifact[];
|
|
7
|
+
/** Refuses to prove when an installed circuit differs from the pinned release. */
|
|
8
|
+
export declare function verifyArtifactManifest(artifactDirectory: string, manifest?: readonly CircuitArtifact[]): Promise<readonly CircuitArtifact[]>;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { readFile } from 'node:fs/promises';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
/** Version-pinned contents of the circuits directory shipped in this package. */
|
|
5
|
+
export const CIRCUIT_MANIFEST = [
|
|
6
|
+
{
|
|
7
|
+
file: 'rln_nullifier.wasm',
|
|
8
|
+
sha256: '38370baebbafb76562d5c94e15354c04152c388bffe1bf85faeeb4dd221538c1',
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
file: 'rln_nullifier_final.zkey',
|
|
12
|
+
sha256: '11cd9e881437283701dcebd75be469d57ec7428de3495d229268eb75f2413cc8',
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
file: 'verification_key_rln.json',
|
|
16
|
+
sha256: '9a521e0e19dc272c281f1a41abf79571bb0094cda653c890bdd95aa5b4c5f1ee',
|
|
17
|
+
},
|
|
18
|
+
];
|
|
19
|
+
/** Refuses to prove when an installed circuit differs from the pinned release. */
|
|
20
|
+
export async function verifyArtifactManifest(artifactDirectory, manifest = CIRCUIT_MANIFEST) {
|
|
21
|
+
for (const artifact of manifest) {
|
|
22
|
+
const content = await readFile(join(artifactDirectory, artifact.file));
|
|
23
|
+
const digest = createHash('sha256').update(content).digest('hex');
|
|
24
|
+
if (digest !== artifact.sha256) {
|
|
25
|
+
throw new Error(`Circuit artifact hash mismatch: ${artifact.file}`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return manifest;
|
|
29
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export interface CliCommandDependencies {
|
|
2
|
+
loopbackBaseUrl: string;
|
|
3
|
+
readToken(): Promise<string>;
|
|
4
|
+
importMnemonic(mnemonic: string): Promise<void>;
|
|
5
|
+
readMnemonic(): Promise<string>;
|
|
6
|
+
write(line: string): void;
|
|
7
|
+
isIdentityConfigured(): Promise<boolean>;
|
|
8
|
+
configureCodex(model?: string): Promise<void>;
|
|
9
|
+
ensureSidecar(): Promise<string>;
|
|
10
|
+
isCodexProfileInstalled(): Promise<boolean>;
|
|
11
|
+
isSidecarHealthy(): Promise<boolean>;
|
|
12
|
+
launchCodex(args: readonly string[]): Promise<number>;
|
|
13
|
+
}
|
|
14
|
+
/** Handles the non-server CLI commands independently from terminal I/O. */
|
|
15
|
+
export declare function runCliCommand(args: readonly string[], dependencies: CliCommandDependencies): Promise<number>;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { formatOpenAiEnvironment } from './sidecar-config.js';
|
|
2
|
+
function setupModel(args) {
|
|
3
|
+
if (args[1] !== 'codex')
|
|
4
|
+
throw new Error('Usage: zk-credits setup codex [--model <model>]');
|
|
5
|
+
if (args.length === 2)
|
|
6
|
+
return undefined;
|
|
7
|
+
if (args.length === 4 && args[2] === '--model' && args[3]?.trim())
|
|
8
|
+
return args[3];
|
|
9
|
+
throw new Error('Usage: zk-credits setup codex [--model <model>]');
|
|
10
|
+
}
|
|
11
|
+
/** Handles the non-server CLI commands independently from terminal I/O. */
|
|
12
|
+
export async function runCliCommand(args, dependencies) {
|
|
13
|
+
switch (args[0]) {
|
|
14
|
+
case 'env': {
|
|
15
|
+
dependencies.write(formatOpenAiEnvironment(dependencies.loopbackBaseUrl, await dependencies.readToken()));
|
|
16
|
+
return 0;
|
|
17
|
+
}
|
|
18
|
+
case 'import-mnemonic': {
|
|
19
|
+
await dependencies.importMnemonic(await dependencies.readMnemonic());
|
|
20
|
+
dependencies.write('ZK Credits identity imported into the system credential store.');
|
|
21
|
+
return 0;
|
|
22
|
+
}
|
|
23
|
+
case 'setup': {
|
|
24
|
+
const model = setupModel(args);
|
|
25
|
+
if (!await dependencies.isIdentityConfigured()) {
|
|
26
|
+
await dependencies.importMnemonic(await dependencies.readMnemonic());
|
|
27
|
+
dependencies.write('ZK Credits identity imported into the system credential store.');
|
|
28
|
+
}
|
|
29
|
+
await dependencies.configureCodex(model);
|
|
30
|
+
await dependencies.ensureSidecar();
|
|
31
|
+
dependencies.write('ZK Credits is ready for Codex.');
|
|
32
|
+
dependencies.write('Run: zk-credits codex');
|
|
33
|
+
return 0;
|
|
34
|
+
}
|
|
35
|
+
case 'token': {
|
|
36
|
+
dependencies.write(await dependencies.ensureSidecar());
|
|
37
|
+
return 0;
|
|
38
|
+
}
|
|
39
|
+
case 'status': {
|
|
40
|
+
dependencies.write(`Identity: ${await dependencies.isIdentityConfigured() ? 'configured' : 'missing'}`);
|
|
41
|
+
dependencies.write(`Codex profile: ${await dependencies.isCodexProfileInstalled() ? 'installed' : 'missing'}`);
|
|
42
|
+
dependencies.write(`Sidecar: ${await dependencies.isSidecarHealthy() ? 'running' : 'stopped'}`);
|
|
43
|
+
return 0;
|
|
44
|
+
}
|
|
45
|
+
case 'codex': {
|
|
46
|
+
if (!await dependencies.isCodexProfileInstalled()) {
|
|
47
|
+
throw new Error('Run zk-credits setup codex first');
|
|
48
|
+
}
|
|
49
|
+
await dependencies.ensureSidecar();
|
|
50
|
+
return dependencies.launchCodex(args.slice(1));
|
|
51
|
+
}
|
|
52
|
+
default:
|
|
53
|
+
throw new Error('Usage: zk-credits <setup codex|codex|status|token|import-mnemonic|serve|env>');
|
|
54
|
+
}
|
|
55
|
+
}
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { launchCodexProcess } from './codex-launcher.js';
|
|
5
|
+
import { isCodexProfileInstalled, resolveCodexHome, writeCodexProfile, } from './codex-profile.js';
|
|
6
|
+
import { IdentityStore } from './identity.js';
|
|
7
|
+
import { createLocalProofGenerator } from './local-prover.js';
|
|
8
|
+
import { MembershipClient } from './membership-client.js';
|
|
9
|
+
import { runCliCommand } from './cli-runtime.js';
|
|
10
|
+
import { createNodeSidecarLifecycle } from './node-sidecar-lifecycle.js';
|
|
11
|
+
import { activateSidecarServer } from './server-startup.js';
|
|
12
|
+
import { createLoopbackToken, sidecarStatePaths } from './sidecar-config.js';
|
|
13
|
+
import { ensureSidecarReady } from './sidecar-lifecycle.js';
|
|
14
|
+
import { readLoopbackToken, writeLoopbackToken } from './sidecar-state.js';
|
|
15
|
+
import { createSidecarServer } from './sidecar.js';
|
|
16
|
+
import { TicketLedger } from './ticket-ledger.js';
|
|
17
|
+
const DEFAULT_GATEWAY_URL = 'https://zk-credits-gateway.onrender.com';
|
|
18
|
+
const DEFAULT_PORT = 3210;
|
|
19
|
+
function stateDirectory() {
|
|
20
|
+
return process.env.ZK_CREDITS_HOME || join(homedir(), '.zk-credits');
|
|
21
|
+
}
|
|
22
|
+
function loopbackBaseUrl() {
|
|
23
|
+
const port = Number(process.env.ZK_CREDITS_SIDECAR_PORT || DEFAULT_PORT);
|
|
24
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535) {
|
|
25
|
+
throw new Error('ZK_CREDITS_SIDECAR_PORT must be a port between 1 and 65535');
|
|
26
|
+
}
|
|
27
|
+
return `http://127.0.0.1:${port}`;
|
|
28
|
+
}
|
|
29
|
+
function readPort(args) {
|
|
30
|
+
const portIndex = args.indexOf('--port');
|
|
31
|
+
if (portIndex === -1)
|
|
32
|
+
return Number(new URL(loopbackBaseUrl()).port);
|
|
33
|
+
const parsed = Number(args[portIndex + 1]);
|
|
34
|
+
if (!Number.isInteger(parsed) || parsed < 1 || parsed > 65535) {
|
|
35
|
+
throw new Error('--port must be a port between 1 and 65535');
|
|
36
|
+
}
|
|
37
|
+
return parsed;
|
|
38
|
+
}
|
|
39
|
+
/** Reads a recovery phrase from a TTY without terminal echo or shell history. */
|
|
40
|
+
async function readHiddenMnemonic() {
|
|
41
|
+
if (!process.stdin.isTTY || !process.stdin.setRawMode) {
|
|
42
|
+
throw new Error('import-mnemonic requires an interactive terminal');
|
|
43
|
+
}
|
|
44
|
+
process.stdout.write('Enter your 24-word recovery phrase: ');
|
|
45
|
+
process.stdin.setRawMode(true);
|
|
46
|
+
process.stdin.resume();
|
|
47
|
+
return new Promise((resolve, reject) => {
|
|
48
|
+
let value = '';
|
|
49
|
+
const done = (error) => {
|
|
50
|
+
process.stdin.off('data', onData);
|
|
51
|
+
process.stdin.setRawMode(false);
|
|
52
|
+
process.stdout.write('\n');
|
|
53
|
+
if (error)
|
|
54
|
+
reject(error);
|
|
55
|
+
else
|
|
56
|
+
resolve(value.trim());
|
|
57
|
+
};
|
|
58
|
+
const onData = (chunk) => {
|
|
59
|
+
for (const byte of chunk) {
|
|
60
|
+
if (byte === 3) {
|
|
61
|
+
done(new Error('Mnemonic import cancelled'));
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
if (byte === 13 || byte === 10) {
|
|
65
|
+
done();
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
if (byte === 127 || byte === 8) {
|
|
69
|
+
value = value.slice(0, -1);
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
value += String.fromCharCode(byte);
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
process.stdin.on('data', onData);
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
function printHelp() {
|
|
79
|
+
console.log(`Usage:
|
|
80
|
+
zk-credits setup codex [--model <model>]
|
|
81
|
+
zk-credits codex [codex arguments...]
|
|
82
|
+
zk-credits status
|
|
83
|
+
zk-credits import-mnemonic
|
|
84
|
+
zk-credits serve [--port <port>]
|
|
85
|
+
eval "$(zk-credits env)"
|
|
86
|
+
|
|
87
|
+
The Codex setup starts the loopback sidecar automatically; daily use is
|
|
88
|
+
"zk-credits codex". serve/env remain available for other OpenAI-compatible
|
|
89
|
+
clients. Set ZK_CREDITS_MNEMONIC only for a headless process; it is not
|
|
90
|
+
persisted by that path.`);
|
|
91
|
+
}
|
|
92
|
+
async function serve(args) {
|
|
93
|
+
const port = readPort(args);
|
|
94
|
+
const statePaths = sidecarStatePaths(stateDirectory());
|
|
95
|
+
const localToken = createLoopbackToken();
|
|
96
|
+
const identities = new IdentityStore();
|
|
97
|
+
const secretK = await identities.loadSecretK({ headlessMnemonic: process.env.ZK_CREDITS_MNEMONIC });
|
|
98
|
+
const gatewayBaseUrl = process.env.ZK_CREDITS_GATEWAY_URL || DEFAULT_GATEWAY_URL;
|
|
99
|
+
const proofGenerator = await createLocalProofGenerator({
|
|
100
|
+
secretK,
|
|
101
|
+
membershipClient: new MembershipClient(gatewayBaseUrl),
|
|
102
|
+
});
|
|
103
|
+
const sidecar = createSidecarServer({
|
|
104
|
+
localToken,
|
|
105
|
+
gatewayBaseUrl,
|
|
106
|
+
compatibilityKey: process.env.ZK_CREDITS_COMPATIBILITY_KEY || 'sk-zk-local-demo',
|
|
107
|
+
ledger: new TicketLedger(statePaths.ledgerPath),
|
|
108
|
+
proofGenerator,
|
|
109
|
+
});
|
|
110
|
+
let address;
|
|
111
|
+
try {
|
|
112
|
+
address = await activateSidecarServer({
|
|
113
|
+
listen: () => sidecar.listen(port),
|
|
114
|
+
publishToken: () => writeLoopbackToken(statePaths.tokenPath, localToken),
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
catch (error) {
|
|
118
|
+
await sidecar.close();
|
|
119
|
+
throw error;
|
|
120
|
+
}
|
|
121
|
+
console.log(`ZK Credits sidecar listening on ${address}/v1`);
|
|
122
|
+
console.log('Run eval "$(zk-credits env)" in the client shell.');
|
|
123
|
+
const shutdown = async () => {
|
|
124
|
+
await sidecar.close();
|
|
125
|
+
process.exit(0);
|
|
126
|
+
};
|
|
127
|
+
process.once('SIGINT', () => { void shutdown(); });
|
|
128
|
+
process.once('SIGTERM', () => { void shutdown(); });
|
|
129
|
+
}
|
|
130
|
+
async function main() {
|
|
131
|
+
const args = process.argv.slice(2);
|
|
132
|
+
if (args[0] === '--help' || args[0] === '-h' || !args[0]) {
|
|
133
|
+
printHelp();
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
if (args[0] === 'serve') {
|
|
137
|
+
await serve(args.slice(1));
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
const identities = new IdentityStore();
|
|
141
|
+
const sidecarHome = stateDirectory();
|
|
142
|
+
const statePaths = sidecarStatePaths(sidecarHome);
|
|
143
|
+
const codexHome = resolveCodexHome(process.env, homedir());
|
|
144
|
+
const cliEntryPath = process.argv[1];
|
|
145
|
+
if (!cliEntryPath)
|
|
146
|
+
throw new Error('Unable to resolve the zk-credits executable path');
|
|
147
|
+
const lifecycle = createNodeSidecarLifecycle({
|
|
148
|
+
loopbackBaseUrl: loopbackBaseUrl(),
|
|
149
|
+
stateDirectory: sidecarHome,
|
|
150
|
+
tokenPath: statePaths.tokenPath,
|
|
151
|
+
logPath: statePaths.logPath,
|
|
152
|
+
cliEntryPath,
|
|
153
|
+
});
|
|
154
|
+
const exitCode = await runCliCommand(args, {
|
|
155
|
+
loopbackBaseUrl: loopbackBaseUrl(),
|
|
156
|
+
readToken: () => readLoopbackToken(statePaths.tokenPath),
|
|
157
|
+
importMnemonic: async (mnemonic) => { await identities.importMnemonic(mnemonic); },
|
|
158
|
+
readMnemonic: readHiddenMnemonic,
|
|
159
|
+
write: (line) => console.log(line),
|
|
160
|
+
isIdentityConfigured: () => identities.hasIdentity(),
|
|
161
|
+
configureCodex: async (model) => {
|
|
162
|
+
await writeCodexProfile({ codexHome, loopbackBaseUrl: loopbackBaseUrl(), model });
|
|
163
|
+
},
|
|
164
|
+
ensureSidecar: () => ensureSidecarReady(lifecycle),
|
|
165
|
+
isCodexProfileInstalled: () => isCodexProfileInstalled(codexHome),
|
|
166
|
+
isSidecarHealthy: () => lifecycle.isHealthy(),
|
|
167
|
+
launchCodex: (codexArgs) => launchCodexProcess(codexArgs),
|
|
168
|
+
});
|
|
169
|
+
if (exitCode !== 0)
|
|
170
|
+
process.exitCode = exitCode;
|
|
171
|
+
}
|
|
172
|
+
void main().catch((error) => {
|
|
173
|
+
console.error(error instanceof Error ? error.message : 'zk-credits failed');
|
|
174
|
+
process.exitCode = 1;
|
|
175
|
+
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
interface SpawnedProcess {
|
|
2
|
+
once(event: 'error', listener: (error: NodeJS.ErrnoException) => void): this;
|
|
3
|
+
once(event: 'exit', listener: (code: number | null, signal: NodeJS.Signals | null) => void): this;
|
|
4
|
+
}
|
|
5
|
+
export type SpawnCommand = (command: string, args: readonly string[], options: {
|
|
6
|
+
stdio: 'inherit';
|
|
7
|
+
}) => SpawnedProcess;
|
|
8
|
+
/** Launches interactive Codex with the isolated ZK Credits provider profile. */
|
|
9
|
+
export declare function launchCodexProcess(args: readonly string[], spawnImpl?: SpawnCommand): Promise<number>;
|
|
10
|
+
export {};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { CODEX_PROFILE_NAME } from './codex-profile.js';
|
|
3
|
+
const spawnCommand = (command, args, options) => spawn(command, args, options);
|
|
4
|
+
/** Launches interactive Codex with the isolated ZK Credits provider profile. */
|
|
5
|
+
export async function launchCodexProcess(args, spawnImpl = spawnCommand) {
|
|
6
|
+
const child = spawnImpl('codex', ['--profile', CODEX_PROFILE_NAME, ...args], { stdio: 'inherit' });
|
|
7
|
+
return new Promise((resolve, reject) => {
|
|
8
|
+
child.once('error', (error) => {
|
|
9
|
+
if (error.code === 'ENOENT') {
|
|
10
|
+
reject(new Error('Codex CLI was not found; install it before running zk-credits codex'));
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
reject(error);
|
|
14
|
+
});
|
|
15
|
+
child.once('exit', (code) => resolve(code ?? 1));
|
|
16
|
+
});
|
|
17
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export declare const CODEX_PROFILE_NAME = "zk-credits";
|
|
2
|
+
export declare const DEFAULT_CODEX_MODEL = "openai/gpt-4o-mini";
|
|
3
|
+
export interface CodexProfileOptions {
|
|
4
|
+
loopbackBaseUrl: string;
|
|
5
|
+
model?: string;
|
|
6
|
+
}
|
|
7
|
+
export interface WriteCodexProfileOptions extends CodexProfileOptions {
|
|
8
|
+
codexHome: string;
|
|
9
|
+
}
|
|
10
|
+
export declare function resolveCodexHome(environment: NodeJS.ProcessEnv, userHome: string): string;
|
|
11
|
+
export declare function codexProfilePath(codexHome: string): string;
|
|
12
|
+
export declare function renderCodexProfile(options: CodexProfileOptions): string;
|
|
13
|
+
/** Returns the Codex-specific model catalog served by its `/v1/models` probe. */
|
|
14
|
+
export declare function codexModelsResponse(): object;
|
|
15
|
+
export declare function writeCodexProfile(options: WriteCodexProfileOptions): Promise<string>;
|
|
16
|
+
export declare function isCodexProfileInstalled(codexHome: string): Promise<boolean>;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { randomBytes } from 'node:crypto';
|
|
2
|
+
import { chmod, mkdir, rename, stat, writeFile } from 'node:fs/promises';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
export const CODEX_PROFILE_NAME = 'zk-credits';
|
|
5
|
+
export const DEFAULT_CODEX_MODEL = 'openai/gpt-4o-mini';
|
|
6
|
+
export function resolveCodexHome(environment, userHome) {
|
|
7
|
+
return environment.CODEX_HOME || join(userHome, '.codex');
|
|
8
|
+
}
|
|
9
|
+
function tomlString(value) {
|
|
10
|
+
return JSON.stringify(value);
|
|
11
|
+
}
|
|
12
|
+
function providerBaseUrl(loopbackBaseUrl) {
|
|
13
|
+
const url = new URL(loopbackBaseUrl);
|
|
14
|
+
if (url.protocol !== 'http:' || url.hostname !== '127.0.0.1') {
|
|
15
|
+
throw new Error('Codex provider must use a 127.0.0.1 HTTP sidecar URL');
|
|
16
|
+
}
|
|
17
|
+
return `${url.origin}/v1`;
|
|
18
|
+
}
|
|
19
|
+
export function codexProfilePath(codexHome) {
|
|
20
|
+
return join(codexHome, `${CODEX_PROFILE_NAME}.config.toml`);
|
|
21
|
+
}
|
|
22
|
+
export function renderCodexProfile(options) {
|
|
23
|
+
const model = options.model?.trim() || DEFAULT_CODEX_MODEL;
|
|
24
|
+
return [
|
|
25
|
+
`model = ${tomlString(model)}`,
|
|
26
|
+
'model_provider = "zk_credits"',
|
|
27
|
+
'',
|
|
28
|
+
'[model_providers.zk_credits]',
|
|
29
|
+
'name = "ZK Credits"',
|
|
30
|
+
`base_url = ${tomlString(providerBaseUrl(options.loopbackBaseUrl))}`,
|
|
31
|
+
'wire_api = "responses"',
|
|
32
|
+
'',
|
|
33
|
+
'[model_providers.zk_credits.auth]',
|
|
34
|
+
'command = "zk-credits"',
|
|
35
|
+
'args = ["token"]',
|
|
36
|
+
'refresh_interval_ms = 0',
|
|
37
|
+
'',
|
|
38
|
+
].join('\n');
|
|
39
|
+
}
|
|
40
|
+
/** Returns the Codex-specific model catalog served by its `/v1/models` probe. */
|
|
41
|
+
export function codexModelsResponse() {
|
|
42
|
+
return {
|
|
43
|
+
models: [{
|
|
44
|
+
slug: DEFAULT_CODEX_MODEL,
|
|
45
|
+
display_name: 'GPT-4o Mini (ZK Credits)',
|
|
46
|
+
description: 'OpenRouter coding model paid with private ZK Credits tickets.',
|
|
47
|
+
default_reasoning_level: null,
|
|
48
|
+
supported_reasoning_levels: [],
|
|
49
|
+
shell_type: 'shell_command',
|
|
50
|
+
visibility: 'list',
|
|
51
|
+
supported_in_api: true,
|
|
52
|
+
priority: 1,
|
|
53
|
+
additional_speed_tiers: [],
|
|
54
|
+
service_tiers: [],
|
|
55
|
+
default_service_tier: null,
|
|
56
|
+
availability_nux: null,
|
|
57
|
+
upgrade: null,
|
|
58
|
+
base_instructions: [
|
|
59
|
+
'You are Codex, a coding agent. Follow the user\'s instructions carefully.',
|
|
60
|
+
'Use the provided tools when needed, preserve existing work, and verify changes before claiming completion.',
|
|
61
|
+
].join(' '),
|
|
62
|
+
include_skills_usage_instructions: true,
|
|
63
|
+
include_plugin_usage_instructions: true,
|
|
64
|
+
include_apps_usage_instructions: true,
|
|
65
|
+
supports_reasoning_summary_parameter: false,
|
|
66
|
+
default_reasoning_summary: 'none',
|
|
67
|
+
support_verbosity: false,
|
|
68
|
+
default_verbosity: null,
|
|
69
|
+
apply_patch_tool_type: 'freeform',
|
|
70
|
+
web_search_tool_type: 'text',
|
|
71
|
+
truncation_policy: { mode: 'tokens', limit: 10_000 },
|
|
72
|
+
supports_parallel_tool_calls: true,
|
|
73
|
+
supports_image_detail_original: false,
|
|
74
|
+
context_window: 128_000,
|
|
75
|
+
max_context_window: 128_000,
|
|
76
|
+
experimental_supported_tools: [],
|
|
77
|
+
input_modalities: ['text', 'image'],
|
|
78
|
+
supports_search_tool: false,
|
|
79
|
+
use_responses_lite: false,
|
|
80
|
+
tool_mode: 'direct',
|
|
81
|
+
}],
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
export async function writeCodexProfile(options) {
|
|
85
|
+
await mkdir(options.codexHome, { recursive: true, mode: 0o700 });
|
|
86
|
+
const profilePath = codexProfilePath(options.codexHome);
|
|
87
|
+
const temporaryPath = `${profilePath}.${process.pid}.${randomBytes(6).toString('hex')}.tmp`;
|
|
88
|
+
await writeFile(temporaryPath, renderCodexProfile(options), { encoding: 'utf8', mode: 0o600 });
|
|
89
|
+
await chmod(temporaryPath, 0o600);
|
|
90
|
+
await rename(temporaryPath, profilePath);
|
|
91
|
+
await chmod(profilePath, 0o600);
|
|
92
|
+
return profilePath;
|
|
93
|
+
}
|
|
94
|
+
export async function isCodexProfileInstalled(codexHome) {
|
|
95
|
+
try {
|
|
96
|
+
return (await stat(codexProfilePath(codexHome))).isFile();
|
|
97
|
+
}
|
|
98
|
+
catch (error) {
|
|
99
|
+
if (error.code === 'ENOENT')
|
|
100
|
+
return false;
|
|
101
|
+
throw error;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export interface CredentialStore {
|
|
2
|
+
getPassword(service: string, account: string): Promise<string | null>;
|
|
3
|
+
setPassword(service: string, account: string, password: string): Promise<void>;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Owns the local private identity. The recovery phrase is converted to the
|
|
7
|
+
* 32-byte secret before persistence, so neither the phrase nor a local bearer
|
|
8
|
+
* ever reaches the gateway or ticket ledger.
|
|
9
|
+
*/
|
|
10
|
+
export declare class IdentityStore {
|
|
11
|
+
private readonly credentials;
|
|
12
|
+
constructor(credentials?: CredentialStore);
|
|
13
|
+
hasIdentity(): Promise<boolean>;
|
|
14
|
+
importMnemonic(mnemonic: string): Promise<Uint8Array>;
|
|
15
|
+
loadSecretK(options?: {
|
|
16
|
+
headlessMnemonic?: string;
|
|
17
|
+
}): Promise<Uint8Array>;
|
|
18
|
+
}
|
package/dist/identity.js
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import * as keytar from 'keytar';
|
|
2
|
+
import { recoverSecretK } from '@zk-credits/shared';
|
|
3
|
+
const SERVICE = 'zk-credits-sidecar';
|
|
4
|
+
const ACCOUNT = 'secret-k-v1';
|
|
5
|
+
function secretToHex(secretK) {
|
|
6
|
+
if (secretK.length !== 32)
|
|
7
|
+
throw new Error('Expected a 32-byte ZK Credits secret');
|
|
8
|
+
return Array.from(secretK, (byte) => byte.toString(16).padStart(2, '0')).join('');
|
|
9
|
+
}
|
|
10
|
+
function hexToSecret(value) {
|
|
11
|
+
if (!/^[a-f0-9]{64}$/i.test(value)) {
|
|
12
|
+
throw new Error('Stored ZK Credits credential is malformed; re-import the mnemonic');
|
|
13
|
+
}
|
|
14
|
+
return new Uint8Array(value.match(/.{2}/g).map((pair) => Number.parseInt(pair, 16)));
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Owns the local private identity. The recovery phrase is converted to the
|
|
18
|
+
* 32-byte secret before persistence, so neither the phrase nor a local bearer
|
|
19
|
+
* ever reaches the gateway or ticket ledger.
|
|
20
|
+
*/
|
|
21
|
+
export class IdentityStore {
|
|
22
|
+
credentials;
|
|
23
|
+
constructor(credentials = keytar) {
|
|
24
|
+
this.credentials = credentials;
|
|
25
|
+
}
|
|
26
|
+
async hasIdentity() {
|
|
27
|
+
return await this.credentials.getPassword(SERVICE, ACCOUNT) !== null;
|
|
28
|
+
}
|
|
29
|
+
async importMnemonic(mnemonic) {
|
|
30
|
+
const secretK = recoverSecretK(mnemonic);
|
|
31
|
+
if (secretK.length !== 32)
|
|
32
|
+
throw new Error('Recovery phrase must encode a 32-byte secret');
|
|
33
|
+
await this.credentials.setPassword(SERVICE, ACCOUNT, secretToHex(secretK));
|
|
34
|
+
return secretK;
|
|
35
|
+
}
|
|
36
|
+
async loadSecretK(options = {}) {
|
|
37
|
+
const stored = await this.credentials.getPassword(SERVICE, ACCOUNT);
|
|
38
|
+
if (stored !== null)
|
|
39
|
+
return hexToSecret(stored);
|
|
40
|
+
if (options.headlessMnemonic) {
|
|
41
|
+
const secretK = recoverSecretK(options.headlessMnemonic);
|
|
42
|
+
if (secretK.length !== 32)
|
|
43
|
+
throw new Error('Recovery phrase must encode a 32-byte secret');
|
|
44
|
+
return secretK;
|
|
45
|
+
}
|
|
46
|
+
throw new Error('No ZK Credits identity found; run zk-credits import-mnemonic first');
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type RlnCircuitResources, type RlnProofInput, type RlnProofResult } from '@zk-credits/shared';
|
|
2
|
+
import { MembershipClient } from './membership-client.js';
|
|
3
|
+
import type { ProofGenerator } from './sidecar.js';
|
|
4
|
+
export interface MembershipWitnessReader {
|
|
5
|
+
witnessForSecret(secretK: Uint8Array): ReturnType<MembershipClient['witnessForSecret']>;
|
|
6
|
+
}
|
|
7
|
+
export type SelfVerifyingRlnProver = (input: RlnProofInput, resources: RlnCircuitResources) => Promise<Pick<RlnProofResult, 'proof' | 'publicSignals'>>;
|
|
8
|
+
export interface LocalProofGeneratorOptions {
|
|
9
|
+
secretK: Uint8Array;
|
|
10
|
+
membershipClient: MembershipWitnessReader;
|
|
11
|
+
artifactDirectory?: string;
|
|
12
|
+
prove?: SelfVerifyingRlnProver;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Builds the sidecar's local prover after checking every installed circuit
|
|
16
|
+
* byte against its release manifest. No proving asset is fetched at runtime.
|
|
17
|
+
*/
|
|
18
|
+
export declare function createLocalProofGenerator(options: LocalProofGeneratorOptions): Promise<ProofGenerator>;
|