@tokamak-private-dapps/private-state-cli 0.1.5 → 0.1.6

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/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.6 - 2026-04-29
4
+
5
+ - Added `--groth16-cli-version` and `--tokamak-zk-evm-cli-version` install options with npm latest defaults.
6
+ - Installed selected proof backend package versions into managed runtime directories.
7
+ - Downloaded Groth16 CRS artifacts matching the selected Groth16 CLI version.
8
+ - Checked channel verifier compatible backend versions before local proof generation.
9
+ - Reported selected proof backend runtime versions from `--doctor`.
10
+
3
11
  ## 0.1.5 - 2026-04-28
4
12
 
5
13
  - Switched channel balance proof generation to invoke `tokamak-groth16 --prove` instead of importing Groth16 proof internals directly.
package/README.md CHANGED
@@ -15,6 +15,15 @@ artifacts:
15
15
  private-state-cli --install
16
16
  ```
17
17
 
18
+ By default, `--install` resolves the latest `@tokamak-zk-evm/cli` and `@tokamak-private-dapps/groth16` versions from
19
+ the npm registry. To pin exact proof backend versions for a channel, pass explicit versions:
20
+
21
+ ```bash
22
+ private-state-cli --install --tokamak-zk-evm-cli-version 2.0.8 --groth16-cli-version 0.1.1
23
+ ```
24
+
25
+ The Groth16 installer downloads the public Google Drive CRS archive with the same version as the selected Groth16 CLI.
26
+
18
27
  `--install` downloads public deployment artifacts from the configured artifact index. It does not read repository-local
19
28
  `deployment/` outputs by default. Repository development workflows that need local anvil artifacts can opt in explicitly:
20
29
 
@@ -52,7 +61,7 @@ A common private-state flow is:
52
61
  Use `private-state-cli --help` for the full command list and required options.
53
62
 
54
63
  `private-state-cli --doctor` reports the CLI package version, dependency versions recorded by the last
55
- `private-state-cli --install`, current dependency versions through `tokamak-l2js`, and Tokamak zk-EVM runtime
64
+ `private-state-cli --install`, selected proof backend runtime versions, current dependency versions through `tokamak-l2js`, and Tokamak zk-EVM runtime
56
65
  install mode, Docker mode, CUDA runtime metadata, live `nvidia-smi` and Docker GPU probe results, and Groth16
57
66
  runtime health. The doctor check fails when the Tokamak Docker `useGpus` metadata does not match the live GPU probes.
58
67
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tokamak-private-dapps/private-state-cli",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
4
4
  "description": "Command-line client for the Tokamak private-state DApp.",
5
5
  "license": "MIT OR Apache-2.0",
6
6
  "author": "Tokamak Network",
@@ -43,8 +43,8 @@
43
43
  "@ethereumjs/util": "^10.1.1",
44
44
  "@noble/curves": "^1.2.0",
45
45
  "@tokamak-private-dapps/common-library": "^0.1.0",
46
- "@tokamak-private-dapps/groth16": "^0.1.1",
47
- "@tokamak-zk-evm/cli": "^2.0.8",
46
+ "@tokamak-private-dapps/groth16": "^0.1.2",
47
+ "@tokamak-zk-evm/cli": "^2.0.12",
48
48
  "ethers": "^6.14.1",
49
49
  "tokamak-l2js": "^0.1.3"
50
50
  },
@@ -45,11 +45,9 @@ import {
45
45
  } from "@ethereumjs/util";
46
46
  import { deriveRpcUrl, resolveCliNetwork } from "@tokamak-private-dapps/common-library/network-config";
47
47
  import {
48
- buildTokamakCliInvocation,
49
48
  resolveTokamakBlockInputConfig,
50
- resolveTokamakCliPackageRoot,
51
- resolveTokamakCliResourceDir,
52
- resolveTokamakCliRuntimeRoot,
49
+ resolveTokamakCliEntryPath,
50
+ resolveTokamakCliPackageRoot as resolveBundledTokamakCliPackageRoot,
53
51
  } from "@tokamak-private-dapps/common-library/tokamak-runtime-paths";
54
52
  import {
55
53
  DEFAULT_PUBLIC_ARTIFACT_INDEX_FILE_ID,
@@ -66,6 +64,7 @@ import { toGroth16SolidityProof } from "@tokamak-private-dapps/common-library/gr
66
64
  import {
67
65
  PUBLIC_GROTH16_MPC_DRIVE_FOLDER_ID,
68
66
  downloadLatestPublicGroth16MpcArtifacts,
67
+ downloadPublicGroth16MpcArtifactsByVersion,
69
68
  } from "@tokamak-private-dapps/groth16/public-drive-crs";
70
69
  import {
71
70
  CHANNEL_BOUND_L2_DERIVATION_MODE,
@@ -85,17 +84,20 @@ const require = createRequire(import.meta.url);
85
84
  const defaultCommandCwd = process.cwd();
86
85
  const privateStateCliPackageRoot = path.dirname(require.resolve("./package.json"));
87
86
  const workspaceRoot = path.resolve(os.homedir(), "tokamak-private-channels", "workspace");
88
- const tokamakCliInvocation = buildTokamakCliInvocation();
89
- const tokamakCliCommand = tokamakCliInvocation.command;
90
- const tokamakCliBaseArgs = tokamakCliInvocation.args;
91
87
  const flatDeploymentArtifactPathsByChainId = new Map();
92
88
  const DOCKER_CUDA_PROBE_IMAGE = "nvidia/cuda:12.2.0-base-ubuntu22.04";
93
89
  const DOCTOR_GPU_PROBE_TIMEOUT_MS = 120000;
90
+ const GROTH16_PACKAGE_NAME = "@tokamak-private-dapps/groth16";
91
+ const TOKAMAK_ZKEVM_CLI_PACKAGE_NAME = "@tokamak-zk-evm/cli";
94
92
 
95
93
  const abiCoder = AbiCoder.defaultAbiCoder();
96
94
  const erc20MetadataAbi = [
97
95
  "function decimals() view returns (uint8)",
98
96
  ];
97
+ const channelVerifierVersionAbi = [
98
+ "function grothVerifierCompatibleBackendVersion() view returns (string)",
99
+ "function tokamakVerifierCompatibleBackendVersion() view returns (string)",
100
+ ];
99
101
  const {
100
102
  aPubBlockLength: TOKAMAK_APUB_BLOCK_LENGTH,
101
103
  previousBlockHashCount: TOKAMAK_PREVIOUS_BLOCK_HASH_COUNT,
@@ -952,31 +954,36 @@ function clearWalletRecoveryArtifacts(walletDir) {
952
954
  }
953
955
 
954
956
  async function handleInstallZkEvm({ args }) {
955
- const installArgs = [...tokamakCliBaseArgs, "--install"];
956
- if (args.docker) {
957
- installArgs.push("--docker");
958
- }
959
- run(tokamakCliCommand, installArgs);
960
- const tokamakRuntimeRoot = resolveTokamakCliRuntimeRoot();
961
- const groth16Runtime = installGroth16RuntimeForPrivateState({
957
+ const selectedVersions = await resolvePrivateStateInstallRuntimeVersions(args);
958
+ const tokamakCliRuntime = await installTokamakCliRuntimeForPrivateState({
959
+ version: selectedVersions.tokamak,
960
+ docker: Boolean(args.docker),
961
+ });
962
+ const groth16Runtime = await installGroth16RuntimeForPrivateState({
963
+ version: selectedVersions.groth16,
962
964
  docker: Boolean(args.docker),
963
965
  });
964
966
  const localDeploymentBaseRoot = args.includeLocalArtifacts ? process.cwd() : null;
965
967
  const deploymentArtifacts = await installPrivateStateCliArtifacts({
966
968
  dappName: PRIVATE_STATE_DAPP_LABEL,
967
969
  localDeploymentBaseRoot,
970
+ groth16CrsVersion: selectedVersions.groth16,
968
971
  });
969
972
  const installManifest = writePrivateStateCliInstallManifest({
970
973
  dockerRequested: Boolean(args.docker),
971
974
  includeLocalArtifacts: Boolean(args.includeLocalArtifacts),
972
975
  localDeploymentBaseRoot,
973
976
  deploymentArtifacts,
977
+ selectedVersions,
978
+ tokamakCliRuntime,
974
979
  groth16Runtime,
975
980
  });
976
981
  printJson({
977
982
  action: "install",
978
- tokamakCli: tokamakCliBaseArgs[0],
979
- runtimeRoot: tokamakRuntimeRoot,
983
+ selectedVersions,
984
+ tokamakCli: tokamakCliRuntime.entryPath,
985
+ runtimeRoot: tokamakCliRuntime.runtimeRoot,
986
+ tokamakCliRuntime,
980
987
  groth16Runtime,
981
988
  docker: Boolean(args.docker),
982
989
  includeLocalArtifacts: Boolean(args.includeLocalArtifacts),
@@ -996,12 +1003,13 @@ async function handleInstallZkEvm({ args }) {
996
1003
 
997
1004
  async function handleUninstallZkEvm() {
998
1005
  let runtimeRoot = null;
1006
+ const invocation = buildTokamakCliInvocationForPackageRoot();
999
1007
  try {
1000
- runtimeRoot = resolveTokamakCliRuntimeRoot();
1008
+ runtimeRoot = inspectTokamakCliRuntime({ packageRoot: invocation.packageRoot }).runtimeRoot;
1001
1009
  } catch {
1002
1010
  runtimeRoot = null;
1003
1011
  }
1004
- run(tokamakCliCommand, [...tokamakCliBaseArgs, "--uninstall"]);
1012
+ run(invocation.command, [...invocation.args, "--uninstall"], { cwd: invocation.packageRoot });
1005
1013
 
1006
1014
  printJson({
1007
1015
  action: "uninstall-zk-evm",
@@ -1309,10 +1317,16 @@ async function handleGrothVaultMove({ args, provider, direction }) {
1309
1317
  const contextResult = await loadPreferredWalletChannelContext({ walletContext, provider });
1310
1318
  const context = contextResult.context;
1311
1319
  const network = contextResult.network;
1320
+ const operationName = args.command === "withdraw-channel"
1321
+ ? "withdraw-channel"
1322
+ : direction === "deposit"
1323
+ ? "deposit-channel"
1324
+ : "withdraw";
1312
1325
  expect(
1313
1326
  ethers.toBigInt(walletContext.wallet.channelId) === ethers.toBigInt(context.workspace.channelId),
1314
1327
  "The provided wallet does not belong to the selected channel.",
1315
1328
  );
1329
+ await assertChannelProofBackendVersionCompatibility({ context, operationName });
1316
1330
 
1317
1331
  const { signer, l2Identity } = restoreWalletParticipant(walletContext, provider);
1318
1332
  const amountInput = requireArg(args.amount, "--amount");
@@ -1364,11 +1378,6 @@ async function handleGrothVaultMove({ args, provider, direction }) {
1364
1378
  nextValue = currentValue - amount;
1365
1379
  }
1366
1380
 
1367
- const operationName = args.command === "withdraw-channel"
1368
- ? "withdraw-channel"
1369
- : direction === "deposit"
1370
- ? "deposit-channel"
1371
- : "withdraw";
1372
1381
  const operationDir = createWalletOperationDir(
1373
1382
  walletContext.walletName,
1374
1383
  walletContext.wallet.network,
@@ -3057,6 +3066,7 @@ async function executeWalletTemplateSend({
3057
3066
  }) {
3058
3067
  await assertWorkspaceAlignedWithChain(context, signer.provider);
3059
3068
  assertWalletMatchesChannelContext(wallet, l2Identity, context);
3069
+ await assertChannelProofBackendVersionCompatibility({ context, operationName });
3060
3070
 
3061
3071
  const controllerAbi = readJson(
3062
3072
  requireLatestDappDeployArtifactPath(context.workspace.chainId, path.basename(templatePayload.abiFile)),
@@ -3428,6 +3438,111 @@ async function assertWorkspaceAlignedWithChain(context) {
3428
3438
  );
3429
3439
  }
3430
3440
 
3441
+ async function assertChannelProofBackendVersionCompatibility({ context, operationName }) {
3442
+ const channelVersions = await readChannelVerifierCompatibleBackendVersions(context);
3443
+ const localVersions = readLocalProofBackendPackageVersions();
3444
+ const checks = [
3445
+ {
3446
+ label: "Groth16",
3447
+ packageName: GROTH16_PACKAGE_NAME,
3448
+ channelVersion: channelVersions.groth16,
3449
+ localVersion: localVersions.groth16,
3450
+ },
3451
+ {
3452
+ label: "Tokamak zk-EVM",
3453
+ packageName: TOKAMAK_ZKEVM_CLI_PACKAGE_NAME,
3454
+ channelVersion: channelVersions.tokamak,
3455
+ localVersion: localVersions.tokamak,
3456
+ },
3457
+ ];
3458
+ const mismatches = checks.filter(({ channelVersion, localVersion }) => channelVersion !== localVersion);
3459
+ if (mismatches.length === 0) {
3460
+ return;
3461
+ }
3462
+
3463
+ throw new Error(
3464
+ [
3465
+ `Channel proof backend version mismatch before ${operationName} proof generation.`,
3466
+ `Channel: ${context.workspace.channelName ?? context.workspaceName ?? context.workspace.channelId}.`,
3467
+ ...mismatches.map(({ label, packageName, channelVersion, localVersion }) => (
3468
+ `${label} verifier expects ${packageName} ${channelVersion}, but the local installed package version is ${localVersion}.`
3469
+ )),
3470
+ "Install the matching CLI package versions before generating proofs for this channel.",
3471
+ ].join(" "),
3472
+ );
3473
+ }
3474
+
3475
+ async function readChannelVerifierCompatibleBackendVersions(context) {
3476
+ const channelManagerAddress = getAddress(context.workspace.channelManager);
3477
+ const channelManager = new Contract(
3478
+ channelManagerAddress,
3479
+ channelVerifierVersionAbi,
3480
+ context.channelManager.runner,
3481
+ );
3482
+ try {
3483
+ const [groth16, tokamak] = await Promise.all([
3484
+ channelManager.grothVerifierCompatibleBackendVersion(),
3485
+ channelManager.tokamakVerifierCompatibleBackendVersion(),
3486
+ ]);
3487
+ return {
3488
+ groth16: requireVersionString(groth16, "channel Groth16 verifier compatibleBackendVersion"),
3489
+ tokamak: requireVersionString(tokamak, "channel Tokamak verifier compatibleBackendVersion"),
3490
+ };
3491
+ } catch (error) {
3492
+ throw new Error(
3493
+ [
3494
+ `Unable to read verifier compatibleBackendVersion values from channel manager ${channelManagerAddress}.`,
3495
+ "The target channel must expose grothVerifierCompatibleBackendVersion() and tokamakVerifierCompatibleBackendVersion().",
3496
+ ].join(" "),
3497
+ { cause: error },
3498
+ );
3499
+ }
3500
+ }
3501
+
3502
+ function readLocalProofBackendPackageVersions() {
3503
+ return {
3504
+ groth16: requirePackageReportVersion(
3505
+ readPackageReport({
3506
+ name: GROTH16_PACKAGE_NAME,
3507
+ packageJsonPath: path.join(resolveActiveGroth16PackageRoot(), "package.json"),
3508
+ }),
3509
+ ),
3510
+ tokamak: requirePackageReportVersion(readTokamakCliPackageReport()),
3511
+ };
3512
+ }
3513
+
3514
+ function readTokamakCliPackageReport() {
3515
+ try {
3516
+ return readPackageReport({
3517
+ name: TOKAMAK_ZKEVM_CLI_PACKAGE_NAME,
3518
+ packageJsonPath: path.join(resolveActiveTokamakCliPackageRoot(), "package.json"),
3519
+ });
3520
+ } catch (error) {
3521
+ return {
3522
+ name: TOKAMAK_ZKEVM_CLI_PACKAGE_NAME,
3523
+ version: null,
3524
+ packageRoot: null,
3525
+ error: error.message,
3526
+ ok: false,
3527
+ };
3528
+ }
3529
+ }
3530
+
3531
+ function requirePackageReportVersion(report) {
3532
+ if (!report.version) {
3533
+ throw new Error(
3534
+ `Unable to determine local ${report.name} package version${report.error ? `: ${report.error}` : "."}`,
3535
+ );
3536
+ }
3537
+ return requireVersionString(report.version, `${report.name} package version`);
3538
+ }
3539
+
3540
+ function requireVersionString(value, label) {
3541
+ const normalized = String(value ?? "").trim();
3542
+ expect(normalized.length > 0, `${label} is missing.`);
3543
+ return normalized;
3544
+ }
3545
+
3431
3546
  async function buildGrothTransition({ operationDir, workspace, stateManager, vaultAddress, keyHex, nextValue }) {
3432
3547
  const vaultAddressObj = createAddressFromString(vaultAddress);
3433
3548
  const keyBigInt = ethers.toBigInt(keyHex);
@@ -3504,7 +3619,7 @@ function runCaptured(command, args, { cwd = defaultCommandCwd, env = process.env
3504
3619
  }
3505
3620
 
3506
3621
  function runGroth16UpdateTreeProof(inputPath) {
3507
- const packageRoot = resolveGroth16PackageRoot();
3622
+ const packageRoot = resolveActiveGroth16PackageRoot();
3508
3623
  const entryPath = resolveGroth16CliEntryPath(packageRoot);
3509
3624
  run(process.execPath, [entryPath, "--prove", inputPath], { cwd: packageRoot });
3510
3625
  const manifestPath = groth16ProofManifestPath();
@@ -3557,7 +3672,8 @@ function printLocalProofGenerationNotice(operationName) {
3557
3672
  }
3558
3673
 
3559
3674
  function runTokamakCliStage({ operationDir, stageName, args }) {
3560
- const result = runCaptured(tokamakCliCommand, [...tokamakCliBaseArgs, ...args]);
3675
+ const invocation = buildTokamakCliInvocationForPackageRoot();
3676
+ const result = runCaptured(invocation.command, [...invocation.args, ...args], { cwd: invocation.packageRoot });
3561
3677
  const logPath = writeTokamakCliStageLog(operationDir, stageName, result);
3562
3678
  if (result.status !== 0) {
3563
3679
  throw new Error(
@@ -3616,6 +3732,7 @@ function findTokamakConsoleError({ stdout, stderr }) {
3616
3732
  function copyTokamakOperationArtifacts(operationDir) {
3617
3733
  const resourceRoot = path.join(operationDir, "resource");
3618
3734
  fs.rmSync(resourceRoot, { recursive: true, force: true });
3735
+ const runtimeRoot = requireActiveTokamakCliRuntimeRoot();
3619
3736
 
3620
3737
  const requiredFiles = [
3621
3738
  ["preprocess", "output", "preprocess.json"],
@@ -3625,7 +3742,7 @@ function copyTokamakOperationArtifacts(operationDir) {
3625
3742
  ];
3626
3743
 
3627
3744
  for (const segments of requiredFiles) {
3628
- const sourcePath = resolveTokamakCliResourceDir(...segments);
3745
+ const sourcePath = resolveTokamakCliResourceDirForRuntimeRoot(runtimeRoot, ...segments);
3629
3746
  const targetPath = path.join(resourceRoot, ...segments);
3630
3747
  ensureDir(path.dirname(targetPath));
3631
3748
  fs.copyFileSync(sourcePath, targetPath);
@@ -4589,9 +4706,23 @@ function assertInstallZkEvmArgs(args) {
4589
4706
  assertAllowedCommandKeys(
4590
4707
  args,
4591
4708
  "--install",
4592
- new Set(["command", "positional", "install", "docker", "includeLocalArtifacts"]),
4593
- "optional --docker and --include-local-artifacts",
4709
+ new Set([
4710
+ "command",
4711
+ "positional",
4712
+ "install",
4713
+ "docker",
4714
+ "includeLocalArtifacts",
4715
+ "groth16CliVersion",
4716
+ "tokamakZkEvmCliVersion",
4717
+ ]),
4718
+ "optional --docker, --include-local-artifacts, --groth16-cli-version, and --tokamak-zk-evm-cli-version",
4594
4719
  );
4720
+ if (args.groth16CliVersion !== undefined) {
4721
+ requireSemverVersion(args.groth16CliVersion, "--groth16-cli-version");
4722
+ }
4723
+ if (args.tokamakZkEvmCliVersion !== undefined) {
4724
+ requireSemverVersion(args.tokamakZkEvmCliVersion, "--tokamak-zk-evm-cli-version");
4725
+ }
4595
4726
  }
4596
4727
 
4597
4728
  function assertUninstallZkEvmArgs(args) {
@@ -4801,8 +4932,9 @@ function persistCurrentState(context) {
4801
4932
  function printHelp() {
4802
4933
  console.log(`
4803
4934
  Commands:
4804
- --install [--docker] [--include-local-artifacts]
4935
+ --install [--docker] [--include-local-artifacts] [--groth16-cli-version <VERSION>] [--tokamak-zk-evm-cli-version <VERSION>]
4805
4936
  Install the Tokamak zk-EVM CLI runtime, Groth16 runtime, and private-state deployment artifacts
4937
+ Version options install exact CLI package versions; omitted versions resolve to npm registry latest
4806
4938
  Use --docker on Linux to forward Docker mode to the Tokamak zk-EVM and Groth16 runtimes
4807
4939
  Use --include-local-artifacts to also install local deployment/ artifacts from the current working directory
4808
4940
 
@@ -5065,6 +5197,14 @@ function expect(condition, message) {
5065
5197
  }
5066
5198
  }
5067
5199
 
5200
+ function requireSemverVersion(value, label) {
5201
+ const normalized = requireNonEmptyString(value, label);
5202
+ if (!/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.]+)?(?:\+[0-9A-Za-z.]+)?$/.test(normalized)) {
5203
+ throw new Error(`${label} must be an exact semantic version. Received: ${normalized}`);
5204
+ }
5205
+ return normalized;
5206
+ }
5207
+
5068
5208
  function resolveArtifactCacheBaseRoot(
5069
5209
  cacheBaseRoot = process.env.PRIVATE_STATE_ARTIFACT_CACHE_ROOT
5070
5210
  ?? process.env.TOKAMAK_PRIVATE_CHANNELS_ROOT
@@ -5077,6 +5217,10 @@ function privateStateCliArtifactRoot(cacheBaseRoot = resolveArtifactCacheBaseRoo
5077
5217
  return path.join(resolveArtifactCacheBaseRoot(cacheBaseRoot), "dapps", "private-state");
5078
5218
  }
5079
5219
 
5220
+ function privateStateCliRuntimeRoot(cacheBaseRoot = resolveArtifactCacheBaseRoot()) {
5221
+ return path.join(privateStateCliArtifactRoot(cacheBaseRoot), "runtimes");
5222
+ }
5223
+
5080
5224
  function privateStateCliArtifactChainDir(cacheBaseRoot = resolveArtifactCacheBaseRoot(), chainId) {
5081
5225
  return path.join(privateStateCliArtifactRoot(cacheBaseRoot), `chain-id-${requireChainId(chainId)}`);
5082
5226
  }
@@ -5101,11 +5245,17 @@ function privateStateCliInstallManifestPath(cacheBaseRoot = resolveArtifactCache
5101
5245
  return path.join(privateStateCliArtifactRoot(cacheBaseRoot), "install-manifest.json");
5102
5246
  }
5103
5247
 
5248
+ function readPrivateStateCliInstallManifest(cacheBaseRoot = resolveArtifactCacheBaseRoot()) {
5249
+ return readJsonIfExists(privateStateCliInstallManifestPath(cacheBaseRoot));
5250
+ }
5251
+
5104
5252
  function writePrivateStateCliInstallManifest({
5105
5253
  dockerRequested,
5106
5254
  includeLocalArtifacts,
5107
5255
  localDeploymentBaseRoot,
5108
5256
  deploymentArtifacts,
5257
+ selectedVersions,
5258
+ tokamakCliRuntime,
5109
5259
  groth16Runtime,
5110
5260
  }) {
5111
5261
  const manifestPath = privateStateCliInstallManifestPath(deploymentArtifacts.cacheBaseRoot);
@@ -5121,6 +5271,8 @@ function writePrivateStateCliInstallManifest({
5121
5271
  includeLocalArtifacts,
5122
5272
  localDeploymentBaseRoot,
5123
5273
  artifactCacheRoot: deploymentArtifacts.cacheBaseRoot,
5274
+ selectedVersions,
5275
+ tokamakCliRuntime,
5124
5276
  groth16Runtime,
5125
5277
  installedDeploymentArtifacts: deploymentArtifacts.installed.map((entry) => ({
5126
5278
  chainId: entry.chainId,
@@ -5149,6 +5301,11 @@ function buildDoctorReport() {
5149
5301
  const tokamakCli = inspectTokamakCliRuntime();
5150
5302
  const groth16Runtime = inspectGroth16Runtime();
5151
5303
  const gpuDockerReadiness = inspectGpuDockerReadiness(tokamakCli);
5304
+ const selectedRuntimeVersionCheck = buildSelectedRuntimeVersionCheck({
5305
+ installManifest,
5306
+ tokamakCli,
5307
+ groth16Runtime,
5308
+ });
5152
5309
  const checks = [
5153
5310
  {
5154
5311
  name: "dependency package versions",
@@ -5161,6 +5318,7 @@ function buildDoctorReport() {
5161
5318
  error: entry.error,
5162
5319
  })),
5163
5320
  },
5321
+ selectedRuntimeVersionCheck,
5164
5322
  {
5165
5323
  name: "tokamak zk-evm runtime",
5166
5324
  ok: tokamakCli.installed,
@@ -5214,6 +5372,9 @@ function buildDoctorReport() {
5214
5372
  installedAt: installManifest?.installedAt ?? null,
5215
5373
  dockerRequested: installManifest?.install?.dockerRequested ?? null,
5216
5374
  includeLocalArtifacts: installManifest?.install?.includeLocalArtifacts ?? null,
5375
+ selectedVersions: installManifest?.install?.selectedVersions ?? null,
5376
+ tokamakCliRuntime: installManifest?.install?.tokamakCliRuntime ?? null,
5377
+ groth16Runtime: installManifest?.install?.groth16Runtime ?? null,
5217
5378
  },
5218
5379
  dependencies: dependencyReports,
5219
5380
  tokamakCli,
@@ -5223,17 +5384,212 @@ function buildDoctorReport() {
5223
5384
  };
5224
5385
  }
5225
5386
 
5226
- function installGroth16RuntimeForPrivateState({ docker }) {
5227
- const packageRoot = resolveGroth16PackageRoot();
5387
+ function buildSelectedRuntimeVersionCheck({ installManifest, tokamakCli, groth16Runtime }) {
5388
+ const selectedVersions = installManifest?.install?.selectedVersions ?? null;
5389
+ const details = [
5390
+ {
5391
+ name: TOKAMAK_ZKEVM_CLI_PACKAGE_NAME,
5392
+ selectedVersion: selectedVersions?.tokamak ?? null,
5393
+ installedVersion: tokamakCli.packageVersion ?? null,
5394
+ ok: !selectedVersions?.tokamak || selectedVersions.tokamak === tokamakCli.packageVersion,
5395
+ },
5396
+ {
5397
+ name: GROTH16_PACKAGE_NAME,
5398
+ selectedVersion: selectedVersions?.groth16 ?? null,
5399
+ installedVersion: groth16Runtime.packageVersion ?? null,
5400
+ crsVersion: groth16Runtime.crsVersion ?? null,
5401
+ ok: !selectedVersions?.groth16
5402
+ || (
5403
+ selectedVersions.groth16 === groth16Runtime.packageVersion
5404
+ && selectedVersions.groth16 === groth16Runtime.crsVersion
5405
+ ),
5406
+ },
5407
+ ];
5408
+ return {
5409
+ name: "selected proof backend runtime versions",
5410
+ ok: details.every((entry) => entry.ok),
5411
+ details,
5412
+ };
5413
+ }
5414
+
5415
+ async function resolvePrivateStateInstallRuntimeVersions(args) {
5416
+ const [groth16, tokamak] = await Promise.all([
5417
+ resolveRequestedNpmPackageVersion({
5418
+ packageName: GROTH16_PACKAGE_NAME,
5419
+ requestedVersion: args.groth16CliVersion,
5420
+ optionName: "--groth16-cli-version",
5421
+ }),
5422
+ resolveRequestedNpmPackageVersion({
5423
+ packageName: TOKAMAK_ZKEVM_CLI_PACKAGE_NAME,
5424
+ requestedVersion: args.tokamakZkEvmCliVersion,
5425
+ optionName: "--tokamak-zk-evm-cli-version",
5426
+ }),
5427
+ ]);
5428
+ return { groth16, tokamak };
5429
+ }
5430
+
5431
+ async function resolveRequestedNpmPackageVersion({ packageName, requestedVersion, optionName }) {
5432
+ const metadata = await fetchNpmPackageMetadata(packageName);
5433
+ if (requestedVersion === undefined || requestedVersion === null) {
5434
+ return requireSemverVersion(metadata?.["dist-tags"]?.latest, `${packageName} npm latest version`);
5435
+ }
5436
+
5437
+ const normalizedVersion = requireSemverVersion(requestedVersion, optionName);
5438
+ if (!metadata.versions?.[normalizedVersion]) {
5439
+ throw new Error(`npm package ${packageName} does not contain version ${normalizedVersion}.`);
5440
+ }
5441
+ return normalizedVersion;
5442
+ }
5443
+
5444
+ async function fetchNpmPackageMetadata(packageName) {
5445
+ const normalizedPackageName = requireNonEmptyString(packageName, "packageName");
5446
+ const registryUrl = `https://registry.npmjs.org/${encodeURIComponent(normalizedPackageName)}`;
5447
+ const response = await fetch(registryUrl, { redirect: "follow" });
5448
+ if (!response.ok) {
5449
+ throw new Error(`Failed to read npm package metadata for ${normalizedPackageName}: HTTP ${response.status}.`);
5450
+ }
5451
+ try {
5452
+ return await response.json();
5453
+ } catch (error) {
5454
+ throw new Error(`npm package metadata for ${normalizedPackageName} is not valid JSON: ${error.message}`);
5455
+ }
5456
+ }
5457
+
5458
+ async function installTokamakCliRuntimeForPrivateState({ version, docker }) {
5459
+ const packageInstall = installManagedNpmPackage({
5460
+ packageName: TOKAMAK_ZKEVM_CLI_PACKAGE_NAME,
5461
+ version,
5462
+ });
5463
+ const invocation = buildTokamakCliInvocationForPackageRoot(packageInstall.packageRoot);
5464
+ const installArgs = [...invocation.args, "--install"];
5465
+ if (docker) {
5466
+ installArgs.push("--docker");
5467
+ }
5468
+ run(invocation.command, installArgs, { cwd: packageInstall.packageRoot });
5469
+ const doctor = runCaptured(invocation.command, [...invocation.args, "--doctor"], {
5470
+ cwd: packageInstall.packageRoot,
5471
+ });
5472
+ const doctorOutput = stripAnsi(`${doctor.stdout}${doctor.stderr}`);
5473
+ const runtimeRoot = parseRuntimeRootFromTokamakDoctorOutput(doctorOutput);
5474
+ expect(
5475
+ doctor.status === 0 && runtimeRoot,
5476
+ [
5477
+ "Tokamak zk-EVM CLI install completed, but tokamak-cli --doctor did not report a healthy runtime.",
5478
+ doctorOutput.trim(),
5479
+ ].filter(Boolean).join(" "),
5480
+ );
5481
+ return {
5482
+ packageName: TOKAMAK_ZKEVM_CLI_PACKAGE_NAME,
5483
+ packageVersion: version,
5484
+ packageRoot: packageInstall.packageRoot,
5485
+ entryPath: invocation.entryPath,
5486
+ installPrefix: packageInstall.installPrefix,
5487
+ runtimeRoot,
5488
+ dockerRequested: Boolean(docker),
5489
+ };
5490
+ }
5491
+
5492
+ async function installGroth16RuntimeForPrivateState({ version, docker }) {
5493
+ const packageInstall = installManagedNpmPackage({
5494
+ packageName: GROTH16_PACKAGE_NAME,
5495
+ version,
5496
+ });
5497
+ const packageRoot = packageInstall.packageRoot;
5228
5498
  const entryPath = resolveGroth16CliEntryPath(packageRoot);
5229
- const args = [entryPath, "--install"];
5499
+ const args = [entryPath, "--install", "--no-setup"];
5230
5500
  if (docker) {
5231
5501
  args.push("--docker");
5232
5502
  }
5233
5503
  run(process.execPath, args, { cwd: packageRoot });
5234
- const runtime = inspectGroth16Runtime();
5504
+ const crsInstall = await installGroth16CrsForPrivateStateVersion(version);
5505
+ const runtime = inspectGroth16Runtime({ packageRoot });
5235
5506
  expect(runtime.installed, "Groth16 runtime install completed, but tokamak-groth16 --doctor still reports an unhealthy runtime.");
5236
- return runtime;
5507
+ return {
5508
+ ...runtime,
5509
+ packageName: GROTH16_PACKAGE_NAME,
5510
+ packageVersion: version,
5511
+ packageRoot,
5512
+ entryPath,
5513
+ installPrefix: packageInstall.installPrefix,
5514
+ crsVersion: crsInstall.version,
5515
+ crs: crsInstall,
5516
+ dockerRequested: Boolean(docker),
5517
+ };
5518
+ }
5519
+
5520
+ async function installGroth16CrsForPrivateStateVersion(version) {
5521
+ const workspaceRoot = defaultGroth16WorkspaceRoot();
5522
+ const crsDir = path.join(workspaceRoot, "crs");
5523
+ const crsInstall = await downloadPublicGroth16MpcArtifactsByVersion({
5524
+ version,
5525
+ outputDir: crsDir,
5526
+ selectedFiles: [
5527
+ "circuit_final.zkey",
5528
+ "verification_key.json",
5529
+ "metadata.json",
5530
+ "zkey_provenance.json",
5531
+ ],
5532
+ });
5533
+ const manifestPath = path.join(workspaceRoot, "install-manifest.json");
5534
+ const manifest = readJsonIfExists(manifestPath) ?? {};
5535
+ writeJson(manifestPath, {
5536
+ ...manifest,
5537
+ workspaceRoot,
5538
+ crsSource: "public-drive-mpc",
5539
+ crs: crsInstall,
5540
+ });
5541
+ return crsInstall;
5542
+ }
5543
+
5544
+ function installManagedNpmPackage({ packageName, version, cacheBaseRoot = resolveArtifactCacheBaseRoot() }) {
5545
+ const normalizedPackageName = requireNonEmptyString(packageName, "packageName");
5546
+ const normalizedVersion = requireSemverVersion(version, `${normalizedPackageName} version`);
5547
+ const installPrefix = managedNpmPackageInstallPrefix({
5548
+ packageName: normalizedPackageName,
5549
+ version: normalizedVersion,
5550
+ cacheBaseRoot,
5551
+ });
5552
+ fs.mkdirSync(installPrefix, { recursive: true });
5553
+ run("npm", [
5554
+ "install",
5555
+ "--prefix",
5556
+ installPrefix,
5557
+ "--omit=dev",
5558
+ "--no-audit",
5559
+ "--fund=false",
5560
+ `${normalizedPackageName}@${normalizedVersion}`,
5561
+ ]);
5562
+ const packageRoot = path.join(installPrefix, "node_modules", ...normalizedPackageName.split("/"));
5563
+ const packageJsonPath = path.join(packageRoot, "package.json");
5564
+ const packageJson = readJson(packageJsonPath);
5565
+ expect(
5566
+ packageJson.name === normalizedPackageName && packageJson.version === normalizedVersion,
5567
+ `Installed package ${packageJsonPath} does not match ${normalizedPackageName}@${normalizedVersion}.`,
5568
+ );
5569
+ return {
5570
+ packageName: normalizedPackageName,
5571
+ version: normalizedVersion,
5572
+ installPrefix,
5573
+ packageRoot,
5574
+ };
5575
+ }
5576
+
5577
+ function managedNpmPackageInstallPrefix({ packageName, version, cacheBaseRoot = resolveArtifactCacheBaseRoot() }) {
5578
+ const safePackageName = requireNonEmptyString(packageName, "packageName")
5579
+ .replace(/^@/, "")
5580
+ .replace(/[^A-Za-z0-9._-]+/g, "__");
5581
+ return path.join(privateStateCliRuntimeRoot(cacheBaseRoot), "npm", safePackageName, requireSemverVersion(version, "version"));
5582
+ }
5583
+
5584
+ async function downloadGroth16CrsArtifactsForPrivateState({
5585
+ version,
5586
+ outputDir,
5587
+ selectedFiles,
5588
+ }) {
5589
+ if (version === undefined || version === null) {
5590
+ return downloadLatestPublicGroth16MpcArtifacts({ outputDir, selectedFiles });
5591
+ }
5592
+ return downloadPublicGroth16MpcArtifactsByVersion({ version, outputDir, selectedFiles });
5237
5593
  }
5238
5594
 
5239
5595
  function collectDependencyPackageReports(installManifest = null) {
@@ -5244,11 +5600,11 @@ function collectDependencyPackageReports(installManifest = null) {
5244
5600
  );
5245
5601
  const targets = [
5246
5602
  {
5247
- name: "@tokamak-zk-evm/cli",
5248
- packageJsonPath: path.join(resolveTokamakCliPackageRoot(), "package.json"),
5603
+ name: TOKAMAK_ZKEVM_CLI_PACKAGE_NAME,
5604
+ packageJsonPath: path.join(resolveBundledTokamakCliPackageRoot(), "package.json"),
5249
5605
  },
5250
5606
  {
5251
- name: "@tokamak-private-dapps/groth16",
5607
+ name: GROTH16_PACKAGE_NAME,
5252
5608
  resolveTarget: "@tokamak-private-dapps/groth16/public-drive-crs",
5253
5609
  },
5254
5610
  {
@@ -5312,21 +5668,42 @@ function resolveGroth16PackageRoot() {
5312
5668
  return path.dirname(findPackageJsonForName(path.dirname(publicDriveCrsPath), "@tokamak-private-dapps/groth16"));
5313
5669
  }
5314
5670
 
5671
+ function resolveActiveGroth16PackageRoot() {
5672
+ const manifestPackageRoot = readPrivateStateCliInstallManifest()?.install?.groth16Runtime?.packageRoot;
5673
+ if (manifestPackageRoot && fs.existsSync(path.join(manifestPackageRoot, "package.json"))) {
5674
+ return manifestPackageRoot;
5675
+ }
5676
+ return resolveGroth16PackageRoot();
5677
+ }
5678
+
5315
5679
  function resolveGroth16CliEntryPath(packageRoot = resolveGroth16PackageRoot()) {
5316
5680
  return path.join(packageRoot, "cli", "tokamak-groth16-cli.mjs");
5317
5681
  }
5318
5682
 
5319
- function inspectGroth16Runtime() {
5320
- const packageRoot = resolveGroth16PackageRoot();
5683
+ function defaultGroth16WorkspaceRoot() {
5684
+ return path.join(os.homedir(), "tokamak-private-channels", "groth16");
5685
+ }
5686
+
5687
+ function inspectGroth16Runtime({ packageRoot = resolveActiveGroth16PackageRoot() } = {}) {
5321
5688
  const entryPath = resolveGroth16CliEntryPath(packageRoot);
5322
5689
  const doctor = runCaptured(process.execPath, [entryPath, "--doctor", "--verbose"], { cwd: packageRoot });
5323
5690
  const stdout = stripAnsi(doctor.stdout).trim();
5324
5691
  const stderr = stripAnsi(doctor.stderr).trim();
5325
5692
  const report = parseJsonReport(stdout);
5693
+ const workspaceRoot = report?.workspaceRoot ?? defaultGroth16WorkspaceRoot();
5694
+ const workspaceManifest = readJsonIfExists(path.join(workspaceRoot, "install-manifest.json"));
5695
+ const packageReport = readPackageReport({
5696
+ name: GROTH16_PACKAGE_NAME,
5697
+ packageJsonPath: path.join(packageRoot, "package.json"),
5698
+ });
5326
5699
  return {
5327
5700
  installed: doctor.status === 0 && report?.ok === true,
5701
+ packageVersion: packageReport.version,
5328
5702
  packageRoot,
5703
+ entryPath,
5329
5704
  workspaceRoot: report?.workspaceRoot ?? null,
5705
+ crsVersion: workspaceManifest?.crs?.version ?? null,
5706
+ crs: workspaceManifest?.crs ?? null,
5330
5707
  checks: report?.checks ?? [],
5331
5708
  doctor: {
5332
5709
  status: doctor.status,
@@ -5336,9 +5713,41 @@ function inspectGroth16Runtime() {
5336
5713
  };
5337
5714
  }
5338
5715
 
5339
- function inspectTokamakCliRuntime() {
5340
- const doctor = runCaptured(tokamakCliCommand, [...tokamakCliBaseArgs, "--doctor"], {
5341
- cwd: resolveTokamakCliPackageRoot(),
5716
+ function resolveActiveTokamakCliPackageRoot() {
5717
+ const manifestPackageRoot = readPrivateStateCliInstallManifest()?.install?.tokamakCliRuntime?.packageRoot;
5718
+ if (manifestPackageRoot && fs.existsSync(path.join(manifestPackageRoot, "package.json"))) {
5719
+ return manifestPackageRoot;
5720
+ }
5721
+ return resolveBundledTokamakCliPackageRoot();
5722
+ }
5723
+
5724
+ function buildTokamakCliInvocationForPackageRoot(packageRoot = resolveActiveTokamakCliPackageRoot()) {
5725
+ const resolvedPackageRoot = path.resolve(packageRoot);
5726
+ const entryPath = resolvedPackageRoot === resolveBundledTokamakCliPackageRoot()
5727
+ ? resolveTokamakCliEntryPath()
5728
+ : path.join(resolvedPackageRoot, "dist", "cli.js");
5729
+ return {
5730
+ command: process.execPath,
5731
+ args: [entryPath],
5732
+ entryPath,
5733
+ packageRoot: resolvedPackageRoot,
5734
+ };
5735
+ }
5736
+
5737
+ function resolveTokamakCliResourceDirForRuntimeRoot(runtimeRoot, ...segments) {
5738
+ return path.join(runtimeRoot, "resource", ...segments);
5739
+ }
5740
+
5741
+ function requireActiveTokamakCliRuntimeRoot() {
5742
+ const runtime = inspectTokamakCliRuntime();
5743
+ expect(runtime.runtimeRoot, "Unable to resolve the installed Tokamak zk-EVM runtime root. Run --install first.");
5744
+ return runtime.runtimeRoot;
5745
+ }
5746
+
5747
+ function inspectTokamakCliRuntime({ packageRoot = resolveActiveTokamakCliPackageRoot() } = {}) {
5748
+ const invocation = buildTokamakCliInvocationForPackageRoot(packageRoot);
5749
+ const doctor = runCaptured(invocation.command, [...invocation.args, "--doctor"], {
5750
+ cwd: invocation.packageRoot,
5342
5751
  });
5343
5752
  const doctorOutput = stripAnsi(`${doctor.stdout}${doctor.stderr}`);
5344
5753
  const runtimeRoot = parseRuntimeRootFromTokamakDoctorOutput(doctorOutput);
@@ -5349,12 +5758,13 @@ function inspectTokamakCliRuntime() {
5349
5758
 
5350
5759
  return {
5351
5760
  installed: doctor.status === 0 || installations.length > 0,
5352
- packageRoot: resolveTokamakCliPackageRoot(),
5761
+ packageRoot: invocation.packageRoot,
5762
+ entryPath: invocation.entryPath,
5353
5763
  cacheRoot,
5354
5764
  runtimeRoot,
5355
5765
  packageVersion: readPackageReport({
5356
- name: "@tokamak-zk-evm/cli",
5357
- packageJsonPath: path.join(resolveTokamakCliPackageRoot(), "package.json"),
5766
+ name: TOKAMAK_ZKEVM_CLI_PACKAGE_NAME,
5767
+ packageJsonPath: path.join(invocation.packageRoot, "package.json"),
5358
5768
  }).version,
5359
5769
  dockerModeInstalled,
5360
5770
  cudaCompatible,
@@ -5495,6 +5905,7 @@ async function installPrivateStateCliArtifacts({
5495
5905
  cacheBaseRoot,
5496
5906
  localDeploymentBaseRoot,
5497
5907
  localChainIds = [31337],
5908
+ groth16CrsVersion,
5498
5909
  } = {}) {
5499
5910
  const normalizedDappName = requireNonEmptyString(dappName, "dappName");
5500
5911
  const normalizedCacheBaseRoot = resolveArtifactCacheBaseRoot(cacheBaseRoot);
@@ -5515,6 +5926,7 @@ async function installPrivateStateCliArtifacts({
5515
5926
  dappName: normalizedDappName,
5516
5927
  cacheBaseRoot: normalizedCacheBaseRoot,
5517
5928
  source: "drive",
5929
+ groth16CrsVersion,
5518
5930
  }));
5519
5931
  }
5520
5932
 
@@ -5525,6 +5937,7 @@ async function installPrivateStateCliArtifacts({
5525
5937
  dappName: normalizedDappName,
5526
5938
  cacheBaseRoot: normalizedCacheBaseRoot,
5527
5939
  localDeploymentBaseRoot: normalizedLocalDeploymentBaseRoot,
5940
+ groth16CrsVersion,
5528
5941
  }));
5529
5942
  }
5530
5943
  }
@@ -5546,6 +5959,7 @@ async function materializePrivateStateCliDeployment({
5546
5959
  dappName,
5547
5960
  cacheBaseRoot,
5548
5961
  source,
5962
+ groth16CrsVersion,
5549
5963
  }) {
5550
5964
  const normalizedChainId = String(requireChainId(chainId));
5551
5965
  const normalizedDappName = requireNonEmptyString(dappName, "dappName");
@@ -5577,7 +5991,8 @@ async function materializePrivateStateCliDeployment({
5577
5991
  [`groth16.${normalizedChainId}.latest.json`, path.basename(paths.grothManifestPath)],
5578
5992
  ],
5579
5993
  });
5580
- await downloadLatestPublicGroth16MpcArtifacts({
5994
+ await downloadGroth16CrsArtifactsForPrivateState({
5995
+ version: groth16CrsVersion,
5581
5996
  outputDir: paths.rootDir,
5582
5997
  selectedFiles: [
5583
5998
  ["circuit_final.zkey", path.basename(paths.grothZkeyPath)],
@@ -5609,6 +6024,7 @@ async function materializeLocalPrivateStateCliDeployment({
5609
6024
  dappName,
5610
6025
  cacheBaseRoot,
5611
6026
  localDeploymentBaseRoot,
6027
+ groth16CrsVersion,
5612
6028
  }) {
5613
6029
  const normalizedChainId = String(requireChainId(chainId));
5614
6030
  const normalizedDappName = requireNonEmptyString(dappName, "dappName");
@@ -5645,7 +6061,8 @@ async function materializeLocalPrivateStateCliDeployment({
5645
6061
  [path.join(dappDir, `dapp-registration.${normalizedChainId}.json`), path.basename(paths.dappRegistrationPath)],
5646
6062
  ],
5647
6063
  });
5648
- await downloadLatestPublicGroth16MpcArtifacts({
6064
+ await downloadGroth16CrsArtifactsForPrivateState({
6065
+ version: groth16CrsVersion,
5649
6066
  outputDir: paths.rootDir,
5650
6067
  selectedFiles: [
5651
6068
  ["circuit_final.zkey", path.basename(paths.grothZkeyPath)],