@sip-protocol/cli 0.2.0 → 0.2.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/dist/index.js +904 -40
- package/dist/index.mjs +924 -43
- package/package.json +10 -8
package/dist/index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
|
-
import { Command as
|
|
4
|
+
import { Command as Command14 } from "commander";
|
|
5
5
|
|
|
6
6
|
// src/commands/init.ts
|
|
7
7
|
import { Command } from "commander";
|
|
@@ -63,6 +63,13 @@ ${message}
|
|
|
63
63
|
function keyValue(key, value) {
|
|
64
64
|
console.log(chalk.gray(` ${key}:`), chalk.white(String(value)));
|
|
65
65
|
}
|
|
66
|
+
function json(data) {
|
|
67
|
+
console.log(JSON.stringify(
|
|
68
|
+
data,
|
|
69
|
+
(_, value) => typeof value === "bigint" ? value.toString() : value,
|
|
70
|
+
2
|
|
71
|
+
));
|
|
72
|
+
}
|
|
66
73
|
function spinner(text) {
|
|
67
74
|
return ora(text).start();
|
|
68
75
|
}
|
|
@@ -129,35 +136,85 @@ import {
|
|
|
129
136
|
isEd25519Chain,
|
|
130
137
|
encodeStealthMetaAddress
|
|
131
138
|
} from "@sip-protocol/sdk";
|
|
139
|
+
import * as fs from "fs";
|
|
140
|
+
import * as path from "path";
|
|
141
|
+
function formatKeysAsText(data) {
|
|
142
|
+
return [
|
|
143
|
+
`# SIP Stealth Meta-Address Keys`,
|
|
144
|
+
`# Generated: ${data.generatedAt}`,
|
|
145
|
+
`# Chain: ${data.chain}`,
|
|
146
|
+
``,
|
|
147
|
+
`## Public Keys (safe to share)`,
|
|
148
|
+
`SPENDING_PUBLIC_KEY=${data.spendingPublicKey}`,
|
|
149
|
+
`VIEWING_PUBLIC_KEY=${data.viewingPublicKey}`,
|
|
150
|
+
`META_ADDRESS=${data.metaAddress}`,
|
|
151
|
+
``,
|
|
152
|
+
`## Private Keys (KEEP SECRET!)`,
|
|
153
|
+
`SPENDING_PRIVATE_KEY=${data.spendingPrivateKey}`,
|
|
154
|
+
`VIEWING_PRIVATE_KEY=${data.viewingPrivateKey}`,
|
|
155
|
+
``,
|
|
156
|
+
`# WARNING: Delete this file after securely storing the keys!`
|
|
157
|
+
].join("\n");
|
|
158
|
+
}
|
|
132
159
|
function createKeygenCommand() {
|
|
133
|
-
return new Command2("keygen").description("Generate stealth meta-address").option("-c, --chain <chain>", "Target chain (ethereum, solana, near)", "ethereum").option("--spending-key <key>", "Spending private key (hex)").option("--viewing-key <key>", "Viewing private key (hex)").action(async (options) => {
|
|
160
|
+
return new Command2("keygen").description("Generate stealth meta-address").option("-c, --chain <chain>", "Target chain (ethereum, solana, near)", "ethereum").option("--spending-key <key>", "Spending private key (hex)").option("--viewing-key <key>", "Viewing private key (hex)").option("-o, --output-file <path>", "Output file for keys (enables secure export)").option("-f, --format <format>", "Output format: json or text", "json").action(async (options) => {
|
|
134
161
|
try {
|
|
135
162
|
heading("Generate Stealth Meta-Address");
|
|
136
163
|
const chain = options.chain;
|
|
137
164
|
const useEd25519 = isEd25519Chain(chain);
|
|
138
|
-
|
|
165
|
+
const format = options.format || "json";
|
|
166
|
+
let result;
|
|
139
167
|
if (useEd25519) {
|
|
140
168
|
if (options.spendingKey || options.viewingKey) {
|
|
141
169
|
warning("Ed25519 chains do not support custom keys in CLI yet");
|
|
142
170
|
}
|
|
143
|
-
|
|
171
|
+
result = generateEd25519StealthMetaAddress(chain);
|
|
144
172
|
} else {
|
|
145
|
-
|
|
173
|
+
result = generateStealthMetaAddress(chain);
|
|
146
174
|
}
|
|
147
175
|
success("Stealth meta-address generated");
|
|
148
176
|
keyValue("Chain", chain);
|
|
149
|
-
const spendingPubKey =
|
|
150
|
-
const viewingPubKey =
|
|
151
|
-
const spendingPrivKey =
|
|
152
|
-
const viewingPrivKey =
|
|
177
|
+
const spendingPubKey = result.metaAddress.spendingKey;
|
|
178
|
+
const viewingPubKey = result.metaAddress.viewingKey;
|
|
179
|
+
const spendingPrivKey = result.spendingPrivateKey;
|
|
180
|
+
const viewingPrivKey = result.viewingPrivateKey;
|
|
181
|
+
const encoded = encodeStealthMetaAddress(result.metaAddress);
|
|
153
182
|
keyValue("Spending Public Key", spendingPubKey);
|
|
154
183
|
keyValue("Viewing Public Key", viewingPubKey);
|
|
155
|
-
const encoded = encodeStealthMetaAddress(metaAddress.metaAddress);
|
|
156
184
|
keyValue("Encoded Address", encoded);
|
|
157
185
|
console.log();
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
186
|
+
if (options.outputFile) {
|
|
187
|
+
const outputPath = path.resolve(options.outputFile);
|
|
188
|
+
const dir = path.dirname(outputPath);
|
|
189
|
+
if (dir !== "." && dir !== "/") {
|
|
190
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
191
|
+
}
|
|
192
|
+
const exportData = {
|
|
193
|
+
chain,
|
|
194
|
+
spendingPublicKey: spendingPubKey,
|
|
195
|
+
viewingPublicKey: viewingPubKey,
|
|
196
|
+
spendingPrivateKey: spendingPrivKey,
|
|
197
|
+
viewingPrivateKey: viewingPrivKey,
|
|
198
|
+
metaAddress: encoded,
|
|
199
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
200
|
+
};
|
|
201
|
+
const content = format === "json" ? JSON.stringify(exportData, null, 2) : formatKeysAsText(exportData);
|
|
202
|
+
fs.writeFileSync(outputPath, content, {
|
|
203
|
+
mode: 384,
|
|
204
|
+
encoding: "utf-8"
|
|
205
|
+
});
|
|
206
|
+
success(`Keys exported to: ${outputPath}`);
|
|
207
|
+
warning("SECURITY: File permissions set to 600 (owner only)");
|
|
208
|
+
warning("SECURITY: Delete this file after securely storing the keys!");
|
|
209
|
+
info("Private keys NOT displayed in terminal for security");
|
|
210
|
+
} else {
|
|
211
|
+
warning("PRIVATE KEYS - Keep these secure!");
|
|
212
|
+
keyValue("Spending Private Key", spendingPrivKey);
|
|
213
|
+
keyValue("Viewing Private Key", viewingPrivKey);
|
|
214
|
+
console.log();
|
|
215
|
+
info("TIP: Use --output-file for secure key export:");
|
|
216
|
+
info(" sip keygen --chain solana --output-file ./keys.json");
|
|
217
|
+
}
|
|
161
218
|
} catch (err) {
|
|
162
219
|
console.error("Failed to generate keys:", err);
|
|
163
220
|
process.exit(1);
|
|
@@ -444,9 +501,40 @@ Solver not found: ${options.solver}`);
|
|
|
444
501
|
|
|
445
502
|
// src/commands/scan.ts
|
|
446
503
|
import { Command as Command8 } from "commander";
|
|
447
|
-
import {
|
|
504
|
+
import {
|
|
505
|
+
checkStealthAddress,
|
|
506
|
+
checkEd25519StealthAddressV1,
|
|
507
|
+
checkSecp256k1StealthAddressV1,
|
|
508
|
+
deriveStealthPrivateKey,
|
|
509
|
+
deriveStealthPrivateKeyV1,
|
|
510
|
+
isEd25519Chain as isEd25519Chain2,
|
|
511
|
+
parseStealthAddress
|
|
512
|
+
} from "@sip-protocol/sdk";
|
|
513
|
+
import { ed25519 } from "@noble/curves/ed25519";
|
|
514
|
+
import { secp256k1 } from "@noble/curves/secp256k1";
|
|
515
|
+
import { bytesToHex, hexToBytes } from "@noble/hashes/utils";
|
|
516
|
+
import * as fs2 from "fs";
|
|
517
|
+
import * as path2 from "path";
|
|
518
|
+
function formatScanResultsAsText(results, exportedAt) {
|
|
519
|
+
const lines = [
|
|
520
|
+
`# SIP Scan Results`,
|
|
521
|
+
`# Exported: ${exportedAt}`,
|
|
522
|
+
`# Found: ${results.length} stealth payment(s)`,
|
|
523
|
+
``
|
|
524
|
+
];
|
|
525
|
+
for (const r of results) {
|
|
526
|
+
lines.push(`## Address: ${r.address}`);
|
|
527
|
+
lines.push(`CHAIN=${r.chain}`);
|
|
528
|
+
if (r.privateKey) {
|
|
529
|
+
lines.push(`PRIVATE_KEY=${r.privateKey}`);
|
|
530
|
+
}
|
|
531
|
+
lines.push(``);
|
|
532
|
+
}
|
|
533
|
+
lines.push(`# WARNING: Delete this file after importing keys to your wallet!`);
|
|
534
|
+
return lines.join("\n");
|
|
535
|
+
}
|
|
448
536
|
function createScanCommand() {
|
|
449
|
-
return new Command8("scan").description("Scan for stealth payments").requiredOption("-c, --chain <chain>", "Chain to scan (ethereum, solana, near)").requiredOption("-s, --spending-key <key>", "Your spending private key (hex)").requiredOption("-v, --viewing-key <key>", "Your viewing private key (hex)").option("-a, --addresses <addresses...>", "Specific addresses to check").action(async (options) => {
|
|
537
|
+
return new Command8("scan").description("Scan for stealth payments").requiredOption("-c, --chain <chain>", "Chain to scan (ethereum, solana, near)").requiredOption("-s, --spending-key <key>", "Your spending private key (hex)").requiredOption("-v, --viewing-key <key>", "Your viewing private key (hex)").option("-a, --addresses <addresses...>", "Specific addresses to check").option("-o, --output-file <path>", "Output file for private keys (required to export keys)").option("-f, --format <format>", "Output format: json or text", "json").action(async (options) => {
|
|
450
538
|
try {
|
|
451
539
|
heading("Scan for Stealth Payments");
|
|
452
540
|
const chain = options.chain;
|
|
@@ -461,24 +549,28 @@ function createScanCommand() {
|
|
|
461
549
|
const results = [];
|
|
462
550
|
for (const address of options.addresses) {
|
|
463
551
|
try {
|
|
552
|
+
const stealthAddr = parseStealthAddress(address);
|
|
464
553
|
let result;
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
)
|
|
554
|
+
const spendingPubKey = useEd25519 ? `0x${bytesToHex(ed25519.getPublicKey(hexToBytes(options.spendingKey.slice(2))))}` : `0x${bytesToHex(secp256k1.getPublicKey(hexToBytes(options.spendingKey.slice(2)), true))}`;
|
|
555
|
+
let isMine = checkStealthAddress(stealthAddr, options.viewingKey, spendingPubKey);
|
|
556
|
+
let isLegacy = false;
|
|
557
|
+
if (!isMine) {
|
|
558
|
+
const legacyMatch = useEd25519 ? checkEd25519StealthAddressV1(stealthAddr, options.spendingKey, options.viewingKey) : checkSecp256k1StealthAddressV1(stealthAddr, options.spendingKey, options.viewingKey);
|
|
559
|
+
if (legacyMatch) {
|
|
560
|
+
isMine = true;
|
|
561
|
+
isLegacy = true;
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
if (isMine) {
|
|
565
|
+
const derivedKey = isLegacy ? deriveStealthPrivateKeyV1(stealthAddr, options.spendingKey, options.viewingKey) : deriveStealthPrivateKey(stealthAddr, options.spendingKey, options.viewingKey);
|
|
566
|
+
result = { isMine: true, stealthPrivateKey: derivedKey.privateKey };
|
|
471
567
|
} else {
|
|
472
|
-
result =
|
|
473
|
-
address,
|
|
474
|
-
options.spendingKey,
|
|
475
|
-
options.viewingKey
|
|
476
|
-
);
|
|
568
|
+
result = { isMine: false };
|
|
477
569
|
}
|
|
478
570
|
results.push({
|
|
479
571
|
address,
|
|
480
572
|
isMine: result.isMine,
|
|
481
|
-
privateKey: result.
|
|
573
|
+
privateKey: result.stealthPrivateKey
|
|
482
574
|
});
|
|
483
575
|
} catch (err) {
|
|
484
576
|
results.push({
|
|
@@ -492,15 +584,34 @@ function createScanCommand() {
|
|
|
492
584
|
success(`Scanned ${results.length} address(es), found ${foundCount} stealth payment(s)`);
|
|
493
585
|
if (foundCount > 0) {
|
|
494
586
|
console.log();
|
|
495
|
-
const headers = ["Address", "Match"
|
|
587
|
+
const headers = ["Address", "Match"];
|
|
496
588
|
const rows = results.filter((r) => r.isMine).map((r) => [
|
|
497
|
-
r.address.slice(0,
|
|
498
|
-
"Yes"
|
|
499
|
-
r.privateKey ? r.privateKey.slice(0, 10) + "..." : "N/A"
|
|
589
|
+
r.address.slice(0, 16) + "..." + r.address.slice(-8),
|
|
590
|
+
"Yes"
|
|
500
591
|
]);
|
|
501
592
|
table(headers, rows);
|
|
502
593
|
console.log();
|
|
503
|
-
|
|
594
|
+
if (options.outputFile) {
|
|
595
|
+
const outputPath = path2.resolve(options.outputFile);
|
|
596
|
+
const format = options.format || "json";
|
|
597
|
+
const exportedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
598
|
+
const exportData = results.filter((r) => r.isMine).map((r) => ({
|
|
599
|
+
address: r.address,
|
|
600
|
+
privateKey: r.privateKey,
|
|
601
|
+
chain: options.chain
|
|
602
|
+
}));
|
|
603
|
+
const content = format === "json" ? JSON.stringify(exportData.map((d) => ({ ...d, exportedAt })), null, 2) : formatScanResultsAsText(exportData, exportedAt);
|
|
604
|
+
fs2.writeFileSync(outputPath, content, {
|
|
605
|
+
mode: 384,
|
|
606
|
+
encoding: "utf-8"
|
|
607
|
+
});
|
|
608
|
+
success(`Private keys exported to: ${outputPath}`);
|
|
609
|
+
warning("SECURITY: Delete this file after importing keys to your wallet!");
|
|
610
|
+
warning("SECURITY: File permissions set to 600 (owner only)");
|
|
611
|
+
} else {
|
|
612
|
+
info("Private keys not exported (use --output-file to export securely)");
|
|
613
|
+
warning("Keys are NOT displayed in terminal for security reasons");
|
|
614
|
+
}
|
|
504
615
|
} else {
|
|
505
616
|
info("No stealth payments found");
|
|
506
617
|
}
|
|
@@ -726,7 +837,7 @@ import {
|
|
|
726
837
|
ed25519PublicKeyToSolanaAddress,
|
|
727
838
|
ed25519PublicKeyToNearAddress,
|
|
728
839
|
publicKeyToEthAddress,
|
|
729
|
-
deriveStealthPrivateKey,
|
|
840
|
+
deriveStealthPrivateKey as deriveStealthPrivateKey2,
|
|
730
841
|
deriveEd25519StealthPrivateKey,
|
|
731
842
|
solanaAddressToEd25519PublicKey
|
|
732
843
|
} from "@sip-protocol/sdk";
|
|
@@ -819,7 +930,7 @@ function createStealthCommand() {
|
|
|
819
930
|
stealthAddressObj,
|
|
820
931
|
spendingKey,
|
|
821
932
|
viewingKey
|
|
822
|
-
) :
|
|
933
|
+
) : deriveStealthPrivateKey2(
|
|
823
934
|
stealthAddressObj,
|
|
824
935
|
spendingKey,
|
|
825
936
|
viewingKey
|
|
@@ -870,8 +981,8 @@ function createViewingKeyCommand() {
|
|
|
870
981
|
const cmd = new Command11("viewing-key").alias("vk").description("Manage viewing keys for selective disclosure");
|
|
871
982
|
cmd.command("generate").alias("gen").description("Generate a new viewing key").option("-p, --path <path>", 'Key derivation path (e.g., "payments/2024")').option("-i, --interactive", "Interactive mode").action(async (options) => {
|
|
872
983
|
heading("Generate Viewing Key");
|
|
873
|
-
let
|
|
874
|
-
if (options.interactive || !
|
|
984
|
+
let path3 = options.path;
|
|
985
|
+
if (options.interactive || !path3) {
|
|
875
986
|
const response = await prompts3([
|
|
876
987
|
{
|
|
877
988
|
type: "text",
|
|
@@ -890,11 +1001,11 @@ function createViewingKeyCommand() {
|
|
|
890
1001
|
console.log(chalk4.yellow("Cancelled."));
|
|
891
1002
|
return;
|
|
892
1003
|
}
|
|
893
|
-
|
|
1004
|
+
path3 = response.path;
|
|
894
1005
|
}
|
|
895
1006
|
const spinner2 = ora4("Generating viewing key...").start();
|
|
896
1007
|
try {
|
|
897
|
-
const viewingKey = generateViewingKey(
|
|
1008
|
+
const viewingKey = generateViewingKey(path3);
|
|
898
1009
|
spinner2.succeed("Viewing key generated");
|
|
899
1010
|
console.log();
|
|
900
1011
|
keyValue("Path", viewingKey.path);
|
|
@@ -919,7 +1030,7 @@ function createViewingKeyCommand() {
|
|
|
919
1030
|
]);
|
|
920
1031
|
if (saveResponse.save) {
|
|
921
1032
|
const existingKeys = getConfig("viewingKeys") || {};
|
|
922
|
-
existingKeys[
|
|
1033
|
+
existingKeys[path3] = viewingKey.key;
|
|
923
1034
|
setConfig("viewingKeys", existingKeys);
|
|
924
1035
|
success("Viewing key saved to config");
|
|
925
1036
|
}
|
|
@@ -940,8 +1051,8 @@ function createViewingKeyCommand() {
|
|
|
940
1051
|
return;
|
|
941
1052
|
}
|
|
942
1053
|
console.log();
|
|
943
|
-
entries.forEach(([
|
|
944
|
-
console.log(chalk4.cyan(` ${
|
|
1054
|
+
entries.forEach(([path3, key]) => {
|
|
1055
|
+
console.log(chalk4.cyan(` ${path3}`));
|
|
945
1056
|
console.log(chalk4.gray(` ${key.slice(0, 20)}...${key.slice(-10)}`));
|
|
946
1057
|
console.log();
|
|
947
1058
|
});
|
|
@@ -961,7 +1072,7 @@ function createViewingKeyCommand() {
|
|
|
961
1072
|
type: "select",
|
|
962
1073
|
name: "path",
|
|
963
1074
|
message: "Select viewing key to share:",
|
|
964
|
-
choices: entries.map(([
|
|
1075
|
+
choices: entries.map(([path3]) => ({ title: path3, value: path3 }))
|
|
965
1076
|
},
|
|
966
1077
|
{
|
|
967
1078
|
type: "select",
|
|
@@ -1004,8 +1115,776 @@ function createViewingKeyCommand() {
|
|
|
1004
1115
|
return cmd;
|
|
1005
1116
|
}
|
|
1006
1117
|
|
|
1118
|
+
// src/commands/backends.ts
|
|
1119
|
+
import { Command as Command12 } from "commander";
|
|
1120
|
+
import {
|
|
1121
|
+
PrivacyBackendRegistry,
|
|
1122
|
+
SIPNativeBackend
|
|
1123
|
+
} from "@sip-protocol/sdk";
|
|
1124
|
+
import chalk5 from "chalk";
|
|
1125
|
+
function createDefaultRegistry() {
|
|
1126
|
+
const registry = new PrivacyBackendRegistry({ enableHealthTracking: true });
|
|
1127
|
+
registry.register(new SIPNativeBackend());
|
|
1128
|
+
return registry;
|
|
1129
|
+
}
|
|
1130
|
+
function collectBackendInfo(registry) {
|
|
1131
|
+
const entries = registry.getAllEntries();
|
|
1132
|
+
const healthTracker = registry.getHealthTracker();
|
|
1133
|
+
const results = [];
|
|
1134
|
+
for (const entry of entries) {
|
|
1135
|
+
const backend = entry.backend;
|
|
1136
|
+
const caps = backend.getCapabilities();
|
|
1137
|
+
let healthy = true;
|
|
1138
|
+
let failures = 0;
|
|
1139
|
+
if (healthTracker) {
|
|
1140
|
+
const health = healthTracker.getHealth(backend.name);
|
|
1141
|
+
if (health) {
|
|
1142
|
+
healthy = health.isHealthy;
|
|
1143
|
+
failures = health.consecutiveFailures;
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
results.push({
|
|
1147
|
+
name: backend.name,
|
|
1148
|
+
type: backend.type,
|
|
1149
|
+
chains: [...backend.chains],
|
|
1150
|
+
healthy,
|
|
1151
|
+
failures,
|
|
1152
|
+
enabled: entry.enabled,
|
|
1153
|
+
compliance: caps.complianceSupport
|
|
1154
|
+
});
|
|
1155
|
+
}
|
|
1156
|
+
return results;
|
|
1157
|
+
}
|
|
1158
|
+
function createBackendsCommand() {
|
|
1159
|
+
const cmd = new Command12("backends").description("Manage and list privacy backends");
|
|
1160
|
+
cmd.command("list").description("List all registered privacy backends").option("--health", "Show health status").option("--metrics", "Show detailed metrics").option("--json", "Output as JSON").option("--type <type>", "Filter by type (transaction, compute, both)").option("--chain <chain>", "Filter by chain support").action(async (options) => {
|
|
1161
|
+
try {
|
|
1162
|
+
const registry = createDefaultRegistry();
|
|
1163
|
+
let backends = collectBackendInfo(registry);
|
|
1164
|
+
if (options.type) {
|
|
1165
|
+
backends = backends.filter((b) => b.type === options.type || b.type === "both");
|
|
1166
|
+
}
|
|
1167
|
+
if (options.chain) {
|
|
1168
|
+
backends = backends.filter((b) => b.chains.includes(options.chain));
|
|
1169
|
+
}
|
|
1170
|
+
if (options.json) {
|
|
1171
|
+
json({
|
|
1172
|
+
backends: backends.map((b) => ({
|
|
1173
|
+
...b,
|
|
1174
|
+
chains: b.chains
|
|
1175
|
+
})),
|
|
1176
|
+
total: backends.length,
|
|
1177
|
+
healthy: backends.filter((b) => b.healthy).length
|
|
1178
|
+
});
|
|
1179
|
+
return;
|
|
1180
|
+
}
|
|
1181
|
+
heading("Privacy Backends");
|
|
1182
|
+
if (backends.length === 0) {
|
|
1183
|
+
warning("No backends match the specified filters");
|
|
1184
|
+
return;
|
|
1185
|
+
}
|
|
1186
|
+
const headers = ["NAME", "TYPE", "CHAINS", "COMPLIANCE"];
|
|
1187
|
+
if (options.health || options.metrics) {
|
|
1188
|
+
headers.push("HEALTHY", "FAILURES");
|
|
1189
|
+
}
|
|
1190
|
+
const rows = backends.map((b) => {
|
|
1191
|
+
const row = [
|
|
1192
|
+
b.enabled ? b.name : chalk5.gray(b.name + " (disabled)"),
|
|
1193
|
+
b.type,
|
|
1194
|
+
b.chains.join(", "),
|
|
1195
|
+
b.compliance ? chalk5.green("\u2713") : chalk5.gray("\u2717")
|
|
1196
|
+
];
|
|
1197
|
+
if (options.health || options.metrics) {
|
|
1198
|
+
row.push(
|
|
1199
|
+
b.healthy ? chalk5.green("\u2713") : chalk5.red("\u2717"),
|
|
1200
|
+
b.failures
|
|
1201
|
+
);
|
|
1202
|
+
}
|
|
1203
|
+
return row;
|
|
1204
|
+
});
|
|
1205
|
+
table(headers, rows);
|
|
1206
|
+
console.log();
|
|
1207
|
+
const healthyCount = backends.filter((b) => b.healthy).length;
|
|
1208
|
+
const complianceCount = backends.filter((b) => b.compliance).length;
|
|
1209
|
+
success(`${backends.length} backend(s) registered`);
|
|
1210
|
+
if (options.health || options.metrics) {
|
|
1211
|
+
if (healthyCount === backends.length) {
|
|
1212
|
+
info(`All backends healthy`);
|
|
1213
|
+
} else {
|
|
1214
|
+
warning(`${healthyCount}/${backends.length} backends healthy`);
|
|
1215
|
+
}
|
|
1216
|
+
}
|
|
1217
|
+
info(`${complianceCount} backend(s) support compliance (viewing keys)`);
|
|
1218
|
+
} catch (err) {
|
|
1219
|
+
console.error("Failed to list backends:", err);
|
|
1220
|
+
process.exit(1);
|
|
1221
|
+
}
|
|
1222
|
+
});
|
|
1223
|
+
cmd.command("info <name>").description("Show detailed information about a specific backend").option("--json", "Output as JSON").action(async (name, options) => {
|
|
1224
|
+
try {
|
|
1225
|
+
const registry = createDefaultRegistry();
|
|
1226
|
+
const backend = registry.get(name);
|
|
1227
|
+
if (!backend) {
|
|
1228
|
+
console.error(`Backend '${name}' not found`);
|
|
1229
|
+
console.error("Available backends:", registry.getNames().join(", "));
|
|
1230
|
+
process.exit(1);
|
|
1231
|
+
}
|
|
1232
|
+
const caps = backend.getCapabilities();
|
|
1233
|
+
const healthTracker = registry.getHealthTracker();
|
|
1234
|
+
const health = healthTracker?.getHealth(name);
|
|
1235
|
+
const metrics = healthTracker?.getMetrics(name);
|
|
1236
|
+
const backendInfo = {
|
|
1237
|
+
name: backend.name,
|
|
1238
|
+
type: backend.type,
|
|
1239
|
+
chains: [...backend.chains],
|
|
1240
|
+
capabilities: {
|
|
1241
|
+
complianceSupport: caps.complianceSupport,
|
|
1242
|
+
anonymitySet: caps.anonymitySet,
|
|
1243
|
+
latency: caps.latencyEstimate,
|
|
1244
|
+
setupRequired: caps.setupRequired
|
|
1245
|
+
},
|
|
1246
|
+
health: health ? {
|
|
1247
|
+
state: health.circuitState,
|
|
1248
|
+
isHealthy: health.isHealthy,
|
|
1249
|
+
consecutiveFailures: health.consecutiveFailures,
|
|
1250
|
+
consecutiveSuccesses: health.consecutiveSuccesses,
|
|
1251
|
+
lastFailureTime: health.lastFailureTime ? new Date(health.lastFailureTime).toISOString() : null,
|
|
1252
|
+
lastFailureReason: health.lastFailureReason ?? null
|
|
1253
|
+
} : null,
|
|
1254
|
+
metrics: metrics ? {
|
|
1255
|
+
totalRequests: metrics.totalRequests,
|
|
1256
|
+
successfulRequests: metrics.successfulRequests,
|
|
1257
|
+
failedRequests: metrics.failedRequests,
|
|
1258
|
+
averageLatencyMs: Math.round(metrics.averageLatencyMs),
|
|
1259
|
+
successRate: metrics.totalRequests > 0 ? Math.round(metrics.successfulRequests / metrics.totalRequests * 100) : 0
|
|
1260
|
+
} : null
|
|
1261
|
+
};
|
|
1262
|
+
if (options.json) {
|
|
1263
|
+
json(backendInfo);
|
|
1264
|
+
return;
|
|
1265
|
+
}
|
|
1266
|
+
heading(`Backend: ${backend.name}`);
|
|
1267
|
+
console.log(chalk5.bold("General"));
|
|
1268
|
+
console.log(` Type: ${backend.type}`);
|
|
1269
|
+
console.log(` Chains: ${backend.chains.join(", ")}`);
|
|
1270
|
+
console.log();
|
|
1271
|
+
console.log(chalk5.bold("Capabilities"));
|
|
1272
|
+
console.log(` Compliance: ${caps.complianceSupport ? chalk5.green("Yes") : chalk5.gray("No")}`);
|
|
1273
|
+
console.log(` Anonymity Set: ${caps.anonymitySet ?? "N/A"}`);
|
|
1274
|
+
console.log(` Est. Latency: ${caps.latencyEstimate}`);
|
|
1275
|
+
console.log(` Setup Required: ${caps.setupRequired ? "Yes" : "No"}`);
|
|
1276
|
+
if (health) {
|
|
1277
|
+
console.log();
|
|
1278
|
+
console.log(chalk5.bold("Health"));
|
|
1279
|
+
console.log(` State: ${health.circuitState === "closed" ? chalk5.green("Healthy") : chalk5.red(health.circuitState)}`);
|
|
1280
|
+
console.log(` Healthy: ${health.isHealthy ? chalk5.green("Yes") : chalk5.red("No")}`);
|
|
1281
|
+
console.log(` Failures: ${health.consecutiveFailures}`);
|
|
1282
|
+
console.log(` Successes: ${health.consecutiveSuccesses}`);
|
|
1283
|
+
if (health.lastFailureReason) {
|
|
1284
|
+
console.log(` Last Error: ${health.lastFailureReason}`);
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
1287
|
+
if (metrics) {
|
|
1288
|
+
console.log();
|
|
1289
|
+
console.log(chalk5.bold("Metrics"));
|
|
1290
|
+
console.log(` Total Requests: ${metrics.totalRequests}`);
|
|
1291
|
+
console.log(` Success Rate: ${backendInfo.metrics?.successRate}%`);
|
|
1292
|
+
console.log(` Avg Latency: ${backendInfo.metrics?.averageLatencyMs}ms`);
|
|
1293
|
+
}
|
|
1294
|
+
} catch (err) {
|
|
1295
|
+
console.error("Failed to get backend info:", err);
|
|
1296
|
+
process.exit(1);
|
|
1297
|
+
}
|
|
1298
|
+
});
|
|
1299
|
+
return cmd;
|
|
1300
|
+
}
|
|
1301
|
+
|
|
1302
|
+
// src/commands/proof.ts
|
|
1303
|
+
import { Command as Command13 } from "commander";
|
|
1304
|
+
import { readFileSync, writeFileSync as writeFileSync3, existsSync } from "fs";
|
|
1305
|
+
import {
|
|
1306
|
+
MockProofProvider as MockProofProvider3,
|
|
1307
|
+
createProofAggregator,
|
|
1308
|
+
createVerificationPipeline,
|
|
1309
|
+
createCrossSystemValidator,
|
|
1310
|
+
UnifiedProofConverter
|
|
1311
|
+
} from "@sip-protocol/sdk";
|
|
1312
|
+
function readProofFile(path3) {
|
|
1313
|
+
if (!existsSync(path3)) {
|
|
1314
|
+
throw new Error(`File not found: ${path3}`);
|
|
1315
|
+
}
|
|
1316
|
+
const content = readFileSync(path3, "utf-8");
|
|
1317
|
+
return JSON.parse(content);
|
|
1318
|
+
}
|
|
1319
|
+
function writeProofFile(path3, data) {
|
|
1320
|
+
writeFileSync3(path3, JSON.stringify(data, null, 2));
|
|
1321
|
+
}
|
|
1322
|
+
function readFromStdin() {
|
|
1323
|
+
return new Promise((resolve3, reject) => {
|
|
1324
|
+
let data = "";
|
|
1325
|
+
process.stdin.setEncoding("utf8");
|
|
1326
|
+
process.stdin.on("data", (chunk) => {
|
|
1327
|
+
data += chunk;
|
|
1328
|
+
});
|
|
1329
|
+
process.stdin.on("end", () => resolve3(data));
|
|
1330
|
+
process.stdin.on("error", reject);
|
|
1331
|
+
setTimeout(() => {
|
|
1332
|
+
if (data === "") {
|
|
1333
|
+
reject(new Error("No input received from stdin"));
|
|
1334
|
+
}
|
|
1335
|
+
}, 1e3);
|
|
1336
|
+
});
|
|
1337
|
+
}
|
|
1338
|
+
var PROOF_SYSTEM_NAMES = {
|
|
1339
|
+
noir: "Noir (Aztec)",
|
|
1340
|
+
halo2: "Halo2 (Zcash)",
|
|
1341
|
+
kimchi: "Kimchi (Mina)",
|
|
1342
|
+
groth16: "Groth16",
|
|
1343
|
+
plonk: "PLONK",
|
|
1344
|
+
stark: "STARK"
|
|
1345
|
+
};
|
|
1346
|
+
function formatProofSystem(system) {
|
|
1347
|
+
return PROOF_SYSTEM_NAMES[system] || system;
|
|
1348
|
+
}
|
|
1349
|
+
function getSystemFromProof(proof) {
|
|
1350
|
+
return proof.metadata.system;
|
|
1351
|
+
}
|
|
1352
|
+
function createProofCommand() {
|
|
1353
|
+
const proof = new Command13("proof").description("Proof composition operations (M20)");
|
|
1354
|
+
proof.command("generate").description("Generate a ZK proof").requiredOption("-s, --system <system>", "Proof system (noir|halo2|kimchi|mock)").requiredOption("-c, --circuit <id>", "Circuit identifier").option("-i, --inputs <json>", "Public inputs as JSON string").option("-f, --inputs-file <path>", "Public inputs from JSON file").option("-w, --witness <json>", "Private witness as JSON string").option("--witness-file <path>", "Private witness from JSON file").option("-o, --output <path>", "Output file path (default: stdout)").option("--json", "Output as JSON", false).action(async (options) => {
|
|
1355
|
+
const format = options.json ? "json" : "human";
|
|
1356
|
+
try {
|
|
1357
|
+
if (format === "human") {
|
|
1358
|
+
heading("Generate ZK Proof");
|
|
1359
|
+
}
|
|
1360
|
+
let publicInputs = {};
|
|
1361
|
+
if (options.inputs) {
|
|
1362
|
+
publicInputs = JSON.parse(options.inputs);
|
|
1363
|
+
} else if (options.inputsFile) {
|
|
1364
|
+
publicInputs = JSON.parse(readFileSync(options.inputsFile, "utf-8"));
|
|
1365
|
+
}
|
|
1366
|
+
const spin = format === "human" ? spinner(`Generating ${options.system} proof...`) : null;
|
|
1367
|
+
const systemLower = options.system.toLowerCase();
|
|
1368
|
+
const provider = new MockProofProvider3({ silent: true });
|
|
1369
|
+
await provider.initialize();
|
|
1370
|
+
const result = await provider.generateFundingProof({
|
|
1371
|
+
balance: BigInt(publicInputs.balance || "1000000"),
|
|
1372
|
+
minimumRequired: BigInt(publicInputs.minimum || "100"),
|
|
1373
|
+
blindingFactor: new Uint8Array(32),
|
|
1374
|
+
assetId: publicInputs.asset || "ETH",
|
|
1375
|
+
userAddress: publicInputs.user || "0x0000000000000000000000000000000000000000",
|
|
1376
|
+
ownershipSignature: new Uint8Array(64)
|
|
1377
|
+
});
|
|
1378
|
+
spin?.succeed("Proof generated");
|
|
1379
|
+
const proofMetadata = {
|
|
1380
|
+
system: systemLower,
|
|
1381
|
+
systemVersion: "1.0.0",
|
|
1382
|
+
circuitId: options.circuit,
|
|
1383
|
+
circuitVersion: "1.0.0",
|
|
1384
|
+
generatedAt: Date.now(),
|
|
1385
|
+
proofSizeBytes: result.proof.proof.length / 2
|
|
1386
|
+
// hex string to bytes
|
|
1387
|
+
};
|
|
1388
|
+
const proofData = {
|
|
1389
|
+
proof: {
|
|
1390
|
+
id: `proof_${Date.now()}`,
|
|
1391
|
+
proof: result.proof.proof,
|
|
1392
|
+
publicInputs: result.publicInputs.map(String),
|
|
1393
|
+
metadata: proofMetadata
|
|
1394
|
+
},
|
|
1395
|
+
metadata: {
|
|
1396
|
+
system: options.system,
|
|
1397
|
+
circuit: options.circuit,
|
|
1398
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1399
|
+
}
|
|
1400
|
+
};
|
|
1401
|
+
if (options.output) {
|
|
1402
|
+
writeProofFile(options.output, proofData);
|
|
1403
|
+
if (format === "human") {
|
|
1404
|
+
success(`Proof written to ${options.output}`);
|
|
1405
|
+
}
|
|
1406
|
+
}
|
|
1407
|
+
if (format === "json") {
|
|
1408
|
+
json(proofData);
|
|
1409
|
+
} else if (!options.output) {
|
|
1410
|
+
keyValue("System", formatProofSystem(systemLower));
|
|
1411
|
+
keyValue("Circuit", options.circuit);
|
|
1412
|
+
keyValue("Proof", formatHash(result.proof.proof, 16));
|
|
1413
|
+
keyValue("Public Inputs", JSON.stringify(result.publicInputs));
|
|
1414
|
+
}
|
|
1415
|
+
} catch (err) {
|
|
1416
|
+
if (format === "json") {
|
|
1417
|
+
json({ error: String(err) });
|
|
1418
|
+
} else {
|
|
1419
|
+
error(`Failed to generate proof: ${err}`);
|
|
1420
|
+
}
|
|
1421
|
+
process.exit(1);
|
|
1422
|
+
}
|
|
1423
|
+
});
|
|
1424
|
+
proof.command("compose").description("Compose multiple proofs together").requiredOption("-p, --proofs <paths...>", "Proof file paths to compose").option("-t, --template <name>", "Composition template (sequential|parallel|recursive)", "sequential").option("-o, --output <path>", "Output file path (default: stdout)").option("--json", "Output as JSON", false).option("--validate", "Validate compatibility before composing", true).action(async (options) => {
|
|
1425
|
+
const format = options.json ? "json" : "human";
|
|
1426
|
+
try {
|
|
1427
|
+
if (format === "human") {
|
|
1428
|
+
heading("Compose Proofs");
|
|
1429
|
+
}
|
|
1430
|
+
const proofs = [];
|
|
1431
|
+
for (const path3 of options.proofs) {
|
|
1432
|
+
const proofFile = readProofFile(path3);
|
|
1433
|
+
proofs.push(proofFile.proof);
|
|
1434
|
+
}
|
|
1435
|
+
if (format === "human") {
|
|
1436
|
+
info(`Loaded ${proofs.length} proofs`);
|
|
1437
|
+
proofs.forEach((p, i) => {
|
|
1438
|
+
keyValue(` Proof ${i + 1}`, `${formatProofSystem(getSystemFromProof(p))} - ${formatHash(p.id)}`);
|
|
1439
|
+
});
|
|
1440
|
+
divider();
|
|
1441
|
+
}
|
|
1442
|
+
if (options.validate) {
|
|
1443
|
+
const validator = createCrossSystemValidator();
|
|
1444
|
+
const systems = [...new Set(proofs.map((p) => getSystemFromProof(p)))];
|
|
1445
|
+
if (systems.length > 1) {
|
|
1446
|
+
const spin2 = format === "human" ? spinner("Validating cross-system compatibility...") : null;
|
|
1447
|
+
const report = validator.validate(proofs, {
|
|
1448
|
+
skipFieldCheck: false,
|
|
1449
|
+
skipCurveCheck: false
|
|
1450
|
+
});
|
|
1451
|
+
const errors = report.checks.filter((c) => !c.passed && c.severity === "error");
|
|
1452
|
+
if (errors.length > 0) {
|
|
1453
|
+
spin2?.fail("Compatibility check failed");
|
|
1454
|
+
if (format === "json") {
|
|
1455
|
+
json({ error: "Incompatible proof systems", report });
|
|
1456
|
+
} else {
|
|
1457
|
+
error("Proof systems are not compatible for composition");
|
|
1458
|
+
errors.forEach((e) => {
|
|
1459
|
+
error(` - ${e.name}: ${e.message}`);
|
|
1460
|
+
});
|
|
1461
|
+
}
|
|
1462
|
+
process.exit(1);
|
|
1463
|
+
}
|
|
1464
|
+
spin2?.succeed("Compatibility validated");
|
|
1465
|
+
}
|
|
1466
|
+
}
|
|
1467
|
+
const spin = format === "human" ? spinner(`Composing proofs (${options.template})...`) : null;
|
|
1468
|
+
const startTime = Date.now();
|
|
1469
|
+
const aggregator = createProofAggregator();
|
|
1470
|
+
const mockProvider = new MockProofProvider3({ silent: true });
|
|
1471
|
+
await mockProvider.initialize();
|
|
1472
|
+
const getProvider = (_system) => {
|
|
1473
|
+
return mockProvider;
|
|
1474
|
+
};
|
|
1475
|
+
let result;
|
|
1476
|
+
if (options.template === "parallel") {
|
|
1477
|
+
result = await aggregator.aggregateParallel({
|
|
1478
|
+
proofs,
|
|
1479
|
+
getProvider,
|
|
1480
|
+
verifyBefore: options.validate,
|
|
1481
|
+
maxConcurrent: 4,
|
|
1482
|
+
onProgress: (event) => {
|
|
1483
|
+
if (format === "human" && spin) {
|
|
1484
|
+
const progress = Math.round(event.step / event.totalSteps * 100);
|
|
1485
|
+
spin.text = `${event.operation} (${progress}%)`;
|
|
1486
|
+
}
|
|
1487
|
+
}
|
|
1488
|
+
});
|
|
1489
|
+
} else {
|
|
1490
|
+
result = await aggregator.aggregateSequential({
|
|
1491
|
+
proofs,
|
|
1492
|
+
getProvider,
|
|
1493
|
+
verifyBefore: options.validate,
|
|
1494
|
+
onProgress: (event) => {
|
|
1495
|
+
if (format === "human" && spin) {
|
|
1496
|
+
const progress = Math.round(event.step / event.totalSteps * 100);
|
|
1497
|
+
spin.text = `${event.operation} (${progress}%)`;
|
|
1498
|
+
}
|
|
1499
|
+
}
|
|
1500
|
+
});
|
|
1501
|
+
}
|
|
1502
|
+
const timeMs = Date.now() - startTime;
|
|
1503
|
+
if (!result.success || !result.composedProof) {
|
|
1504
|
+
spin?.fail("Composition failed");
|
|
1505
|
+
if (format === "json") {
|
|
1506
|
+
json({ error: result.error || "Unknown error" });
|
|
1507
|
+
} else {
|
|
1508
|
+
error(`Composition failed: ${result.error || "Unknown error"}`);
|
|
1509
|
+
}
|
|
1510
|
+
process.exit(1);
|
|
1511
|
+
}
|
|
1512
|
+
spin?.succeed("Composition complete");
|
|
1513
|
+
const outputData = {
|
|
1514
|
+
proof: result.composedProof,
|
|
1515
|
+
metadata: {
|
|
1516
|
+
template: options.template,
|
|
1517
|
+
inputProofs: proofs.map((p) => p.id),
|
|
1518
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1519
|
+
timeMs
|
|
1520
|
+
}
|
|
1521
|
+
};
|
|
1522
|
+
if (options.output) {
|
|
1523
|
+
writeProofFile(options.output, outputData);
|
|
1524
|
+
if (format === "human") {
|
|
1525
|
+
success(`Composed proof written to ${options.output}`);
|
|
1526
|
+
}
|
|
1527
|
+
}
|
|
1528
|
+
if (format === "json") {
|
|
1529
|
+
json(outputData);
|
|
1530
|
+
} else if (!options.output) {
|
|
1531
|
+
keyValue("Composed Proof ID", result.composedProof.id);
|
|
1532
|
+
keyValue("Component Proofs", result.composedProof.proofs.length.toString());
|
|
1533
|
+
keyValue("Strategy", result.composedProof.strategy);
|
|
1534
|
+
keyValue("Time", `${timeMs}ms`);
|
|
1535
|
+
}
|
|
1536
|
+
} catch (err) {
|
|
1537
|
+
if (format === "json") {
|
|
1538
|
+
json({ error: String(err) });
|
|
1539
|
+
} else {
|
|
1540
|
+
error(`Failed to compose proofs: ${err}`);
|
|
1541
|
+
}
|
|
1542
|
+
process.exit(1);
|
|
1543
|
+
}
|
|
1544
|
+
});
|
|
1545
|
+
proof.command("verify").description("Verify a proof or composed proof").argument("<path>", "Proof file path (or - for stdin)").option("--strict", "Enable strict verification mode", false).option("--json", "Output as JSON", false).action(async (path3, options) => {
|
|
1546
|
+
const format = options.json ? "json" : "human";
|
|
1547
|
+
try {
|
|
1548
|
+
if (format === "human") {
|
|
1549
|
+
heading("Verify Proof");
|
|
1550
|
+
}
|
|
1551
|
+
let proofData;
|
|
1552
|
+
if (path3 === "-") {
|
|
1553
|
+
const stdinData = await readFromStdin();
|
|
1554
|
+
proofData = JSON.parse(stdinData);
|
|
1555
|
+
} else {
|
|
1556
|
+
proofData = readProofFile(path3);
|
|
1557
|
+
}
|
|
1558
|
+
const spin = format === "human" ? spinner("Verifying proof...") : null;
|
|
1559
|
+
const pipeline = createVerificationPipeline();
|
|
1560
|
+
const isComposed = "proofs" in proofData.proof && Array.isArray(proofData.proof.proofs);
|
|
1561
|
+
const mockProvider = new MockProofProvider3({ silent: true });
|
|
1562
|
+
await mockProvider.initialize();
|
|
1563
|
+
const getProvider = (_system) => {
|
|
1564
|
+
return mockProvider;
|
|
1565
|
+
};
|
|
1566
|
+
let result;
|
|
1567
|
+
if (isComposed) {
|
|
1568
|
+
result = await pipeline.verify(proofData.proof, {
|
|
1569
|
+
getProvider,
|
|
1570
|
+
config: {}
|
|
1571
|
+
});
|
|
1572
|
+
} else {
|
|
1573
|
+
result = await pipeline.verifySingle(proofData.proof, getProvider);
|
|
1574
|
+
}
|
|
1575
|
+
if (result.valid) {
|
|
1576
|
+
spin?.succeed("Proof is valid");
|
|
1577
|
+
} else {
|
|
1578
|
+
spin?.fail("Proof verification failed");
|
|
1579
|
+
}
|
|
1580
|
+
if (format === "json") {
|
|
1581
|
+
json({
|
|
1582
|
+
valid: result.valid,
|
|
1583
|
+
isComposed,
|
|
1584
|
+
details: result
|
|
1585
|
+
});
|
|
1586
|
+
} else {
|
|
1587
|
+
keyValue("Valid", result.valid ? "Yes" : "No");
|
|
1588
|
+
keyValue("Type", isComposed ? "Composed" : "Single");
|
|
1589
|
+
if (isComposed) {
|
|
1590
|
+
const composed = proofData.proof;
|
|
1591
|
+
keyValue("Component Proofs", composed.proofs.length.toString());
|
|
1592
|
+
}
|
|
1593
|
+
if (!result.valid && "error" in result) {
|
|
1594
|
+
error(`Reason: ${result.error}`);
|
|
1595
|
+
}
|
|
1596
|
+
}
|
|
1597
|
+
if (!result.valid) {
|
|
1598
|
+
process.exit(1);
|
|
1599
|
+
}
|
|
1600
|
+
} catch (err) {
|
|
1601
|
+
if (format === "json") {
|
|
1602
|
+
json({ valid: false, error: String(err) });
|
|
1603
|
+
} else {
|
|
1604
|
+
error(`Failed to verify proof: ${err}`);
|
|
1605
|
+
}
|
|
1606
|
+
process.exit(1);
|
|
1607
|
+
}
|
|
1608
|
+
});
|
|
1609
|
+
proof.command("inspect").description("Analyze and display proof details").argument("<path>", "Proof file path (or - for stdin)").option("--json", "Output as JSON", false).option("--full", "Show full proof data (not truncated)", false).action(async (path3, options) => {
|
|
1610
|
+
const format = options.json ? "json" : "human";
|
|
1611
|
+
try {
|
|
1612
|
+
if (format === "human") {
|
|
1613
|
+
heading("Proof Inspection");
|
|
1614
|
+
}
|
|
1615
|
+
let proofData;
|
|
1616
|
+
if (path3 === "-") {
|
|
1617
|
+
const stdinData = await readFromStdin();
|
|
1618
|
+
proofData = JSON.parse(stdinData);
|
|
1619
|
+
} else {
|
|
1620
|
+
proofData = readProofFile(path3);
|
|
1621
|
+
}
|
|
1622
|
+
const proof2 = proofData.proof;
|
|
1623
|
+
const isComposed = "proofs" in proof2 && Array.isArray(proof2.proofs);
|
|
1624
|
+
if (format === "json") {
|
|
1625
|
+
json({
|
|
1626
|
+
type: isComposed ? "composed" : "single",
|
|
1627
|
+
...proofData
|
|
1628
|
+
});
|
|
1629
|
+
return;
|
|
1630
|
+
}
|
|
1631
|
+
keyValue("Type", isComposed ? "Composed Proof" : "Single Proof");
|
|
1632
|
+
keyValue("ID", proof2.id);
|
|
1633
|
+
divider();
|
|
1634
|
+
if (isComposed) {
|
|
1635
|
+
const composed = proof2;
|
|
1636
|
+
keyValue("Strategy", composed.strategy);
|
|
1637
|
+
keyValue("Status", composed.status);
|
|
1638
|
+
keyValue("Component Proofs", composed.proofs.length.toString());
|
|
1639
|
+
info("\nComponent Proofs:");
|
|
1640
|
+
table(
|
|
1641
|
+
["#", "System", "ID", "Public Inputs"],
|
|
1642
|
+
composed.proofs.map((p, i) => [
|
|
1643
|
+
(i + 1).toString(),
|
|
1644
|
+
formatProofSystem(getSystemFromProof(p)),
|
|
1645
|
+
formatHash(p.id),
|
|
1646
|
+
p.publicInputs.length.toString()
|
|
1647
|
+
])
|
|
1648
|
+
);
|
|
1649
|
+
if (composed.compositionMetadata) {
|
|
1650
|
+
divider();
|
|
1651
|
+
info("Composition Metadata:");
|
|
1652
|
+
keyValue(" Proof Count", composed.compositionMetadata.proofCount.toString());
|
|
1653
|
+
keyValue(" Systems", composed.compositionMetadata.systems.join(", "));
|
|
1654
|
+
keyValue(" Composition Time", `${composed.compositionMetadata.compositionTimeMs}ms`);
|
|
1655
|
+
}
|
|
1656
|
+
} else {
|
|
1657
|
+
const single = proof2;
|
|
1658
|
+
keyValue("System", formatProofSystem(getSystemFromProof(single)));
|
|
1659
|
+
if (single.metadata) {
|
|
1660
|
+
keyValue("Circuit ID", single.metadata.circuitId || "N/A");
|
|
1661
|
+
keyValue("Circuit Version", single.metadata.circuitVersion || "N/A");
|
|
1662
|
+
keyValue("System Version", single.metadata.systemVersion || "N/A");
|
|
1663
|
+
keyValue("Proof Size", `${single.metadata.proofSizeBytes} bytes`);
|
|
1664
|
+
if (single.metadata.generatedAt) {
|
|
1665
|
+
keyValue("Generated At", new Date(single.metadata.generatedAt).toISOString());
|
|
1666
|
+
}
|
|
1667
|
+
}
|
|
1668
|
+
divider();
|
|
1669
|
+
info("Proof Data:");
|
|
1670
|
+
if (options.full) {
|
|
1671
|
+
keyValue("Proof", single.proof);
|
|
1672
|
+
} else {
|
|
1673
|
+
keyValue("Proof", formatHash(single.proof, 32));
|
|
1674
|
+
}
|
|
1675
|
+
info("\nPublic Inputs:");
|
|
1676
|
+
if (single.publicInputs && single.publicInputs.length > 0) {
|
|
1677
|
+
single.publicInputs.forEach((input, i) => {
|
|
1678
|
+
keyValue(` [${i}]`, formatHash(input, 16));
|
|
1679
|
+
});
|
|
1680
|
+
} else {
|
|
1681
|
+
info(" (none)");
|
|
1682
|
+
}
|
|
1683
|
+
}
|
|
1684
|
+
if (proofData.metadata) {
|
|
1685
|
+
divider();
|
|
1686
|
+
info("File Metadata:");
|
|
1687
|
+
Object.entries(proofData.metadata).forEach(([key, value]) => {
|
|
1688
|
+
keyValue(` ${key}`, typeof value === "object" ? JSON.stringify(value) : String(value));
|
|
1689
|
+
});
|
|
1690
|
+
}
|
|
1691
|
+
} catch (err) {
|
|
1692
|
+
if (format === "json") {
|
|
1693
|
+
json({ error: String(err) });
|
|
1694
|
+
} else {
|
|
1695
|
+
error(`Failed to inspect proof: ${err}`);
|
|
1696
|
+
}
|
|
1697
|
+
process.exit(1);
|
|
1698
|
+
}
|
|
1699
|
+
});
|
|
1700
|
+
proof.command("convert").description("Convert proof between formats").argument("<path>", "Source proof file path (or - for stdin)").requiredOption("-t, --to <format>", "Target format (sip|noir|halo2|kimchi|json)").option("-o, --output <path>", "Output file path (default: stdout)").option("--json", "Output as JSON", false).action(async (path3, options) => {
|
|
1701
|
+
const format = options.json ? "json" : "human";
|
|
1702
|
+
try {
|
|
1703
|
+
if (format === "human") {
|
|
1704
|
+
heading("Convert Proof Format");
|
|
1705
|
+
}
|
|
1706
|
+
let proofData;
|
|
1707
|
+
if (path3 === "-") {
|
|
1708
|
+
const stdinData = await readFromStdin();
|
|
1709
|
+
proofData = JSON.parse(stdinData);
|
|
1710
|
+
} else {
|
|
1711
|
+
proofData = readProofFile(path3);
|
|
1712
|
+
}
|
|
1713
|
+
const spin = format === "human" ? spinner(`Converting to ${options.to} format...`) : null;
|
|
1714
|
+
const converter = new UnifiedProofConverter();
|
|
1715
|
+
const targetFormat = options.to.toLowerCase();
|
|
1716
|
+
const single = proofData.proof;
|
|
1717
|
+
let result;
|
|
1718
|
+
if (targetFormat === "json") {
|
|
1719
|
+
result = proofData;
|
|
1720
|
+
} else if (targetFormat === "sip") {
|
|
1721
|
+
result = proofData;
|
|
1722
|
+
} else {
|
|
1723
|
+
const converted = converter.fromSIP(single);
|
|
1724
|
+
if (!converted.success || !converted.result) {
|
|
1725
|
+
throw new Error(converted.error || "Conversion failed");
|
|
1726
|
+
}
|
|
1727
|
+
result = converted.result;
|
|
1728
|
+
}
|
|
1729
|
+
spin?.succeed("Conversion complete");
|
|
1730
|
+
const outputData = {
|
|
1731
|
+
originalFormat: getSystemFromProof(single),
|
|
1732
|
+
targetFormat,
|
|
1733
|
+
proof: result,
|
|
1734
|
+
convertedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1735
|
+
};
|
|
1736
|
+
if (options.output) {
|
|
1737
|
+
writeFileSync3(options.output, JSON.stringify(outputData, null, 2));
|
|
1738
|
+
if (format === "human") {
|
|
1739
|
+
success(`Converted proof written to ${options.output}`);
|
|
1740
|
+
}
|
|
1741
|
+
}
|
|
1742
|
+
if (format === "json" || !options.output) {
|
|
1743
|
+
json(outputData);
|
|
1744
|
+
}
|
|
1745
|
+
} catch (err) {
|
|
1746
|
+
if (format === "json") {
|
|
1747
|
+
json({ error: String(err) });
|
|
1748
|
+
} else {
|
|
1749
|
+
error(`Failed to convert proof: ${err}`);
|
|
1750
|
+
}
|
|
1751
|
+
process.exit(1);
|
|
1752
|
+
}
|
|
1753
|
+
});
|
|
1754
|
+
proof.command("systems").description("List supported proof systems and their capabilities").option("--json", "Output as JSON", false).action(async (options) => {
|
|
1755
|
+
const format = options.json ? "json" : "human";
|
|
1756
|
+
const systems = [
|
|
1757
|
+
{
|
|
1758
|
+
id: "noir",
|
|
1759
|
+
name: "Noir (Aztec)",
|
|
1760
|
+
curve: "BN254",
|
|
1761
|
+
features: ["SNARK", "Universal Setup", "Browser Support"],
|
|
1762
|
+
status: "Production"
|
|
1763
|
+
},
|
|
1764
|
+
{
|
|
1765
|
+
id: "halo2",
|
|
1766
|
+
name: "Halo2 (Zcash)",
|
|
1767
|
+
curve: "Pallas/Vesta",
|
|
1768
|
+
features: ["SNARK", "No Trusted Setup", "Recursive"],
|
|
1769
|
+
status: "Production"
|
|
1770
|
+
},
|
|
1771
|
+
{
|
|
1772
|
+
id: "kimchi",
|
|
1773
|
+
name: "Kimchi (Mina)",
|
|
1774
|
+
curve: "Pasta",
|
|
1775
|
+
features: ["SNARK", "Recursive", "Succinct"],
|
|
1776
|
+
status: "Production"
|
|
1777
|
+
},
|
|
1778
|
+
{
|
|
1779
|
+
id: "groth16",
|
|
1780
|
+
name: "Groth16",
|
|
1781
|
+
curve: "BN254",
|
|
1782
|
+
features: ["SNARK", "Smallest Proofs", "Fastest Verification"],
|
|
1783
|
+
status: "Supported"
|
|
1784
|
+
},
|
|
1785
|
+
{
|
|
1786
|
+
id: "plonk",
|
|
1787
|
+
name: "PLONK",
|
|
1788
|
+
curve: "BN254",
|
|
1789
|
+
features: ["SNARK", "Universal Setup", "Flexible"],
|
|
1790
|
+
status: "Supported"
|
|
1791
|
+
},
|
|
1792
|
+
{
|
|
1793
|
+
id: "stark",
|
|
1794
|
+
name: "STARK",
|
|
1795
|
+
curve: "None (Hash-based)",
|
|
1796
|
+
features: ["No Trusted Setup", "Post-Quantum", "Large Proofs"],
|
|
1797
|
+
status: "Experimental"
|
|
1798
|
+
}
|
|
1799
|
+
];
|
|
1800
|
+
if (format === "json") {
|
|
1801
|
+
json({ systems });
|
|
1802
|
+
return;
|
|
1803
|
+
}
|
|
1804
|
+
heading("Supported Proof Systems");
|
|
1805
|
+
table(
|
|
1806
|
+
["System", "Curve", "Status"],
|
|
1807
|
+
systems.map((s) => [s.name, s.curve, s.status])
|
|
1808
|
+
);
|
|
1809
|
+
divider();
|
|
1810
|
+
info("Features by System:");
|
|
1811
|
+
systems.forEach((s) => {
|
|
1812
|
+
keyValue(` ${s.name}`, s.features.join(", "));
|
|
1813
|
+
});
|
|
1814
|
+
});
|
|
1815
|
+
proof.command("compat").description("Check compatibility between proof systems").argument("<systems...>", "Proof systems to check (e.g., noir halo2)").option("--json", "Output as JSON", false).action(async (systems, options) => {
|
|
1816
|
+
const format = options.json ? "json" : "human";
|
|
1817
|
+
try {
|
|
1818
|
+
if (format === "human") {
|
|
1819
|
+
heading("Proof System Compatibility");
|
|
1820
|
+
}
|
|
1821
|
+
const mockProofs = systems.map((sys, i) => ({
|
|
1822
|
+
id: `mock_${sys}_${i}`,
|
|
1823
|
+
proof: "0x00",
|
|
1824
|
+
publicInputs: ["0x01"],
|
|
1825
|
+
metadata: {
|
|
1826
|
+
system: sys,
|
|
1827
|
+
systemVersion: "1.0.0",
|
|
1828
|
+
circuitId: "test",
|
|
1829
|
+
circuitVersion: "1.0.0",
|
|
1830
|
+
generatedAt: Date.now(),
|
|
1831
|
+
proofSizeBytes: 1
|
|
1832
|
+
}
|
|
1833
|
+
}));
|
|
1834
|
+
const validator = createCrossSystemValidator();
|
|
1835
|
+
const report = validator.validate(mockProofs, {
|
|
1836
|
+
skipFieldCheck: false,
|
|
1837
|
+
skipCurveCheck: false
|
|
1838
|
+
});
|
|
1839
|
+
const errors = report.checks.filter((c) => !c.passed && c.severity === "error");
|
|
1840
|
+
const warnings = report.checks.filter((c) => !c.passed && c.severity === "warning");
|
|
1841
|
+
const compatible = errors.length === 0;
|
|
1842
|
+
if (format === "json") {
|
|
1843
|
+
json({
|
|
1844
|
+
systems,
|
|
1845
|
+
compatible,
|
|
1846
|
+
errors: errors.map((e) => ({ check: e.name, message: e.message })),
|
|
1847
|
+
warnings: warnings.map((w) => ({ check: w.name, message: w.message })),
|
|
1848
|
+
report
|
|
1849
|
+
});
|
|
1850
|
+
return;
|
|
1851
|
+
}
|
|
1852
|
+
keyValue("Systems", systems.join(", "));
|
|
1853
|
+
keyValue("Compatible", compatible ? "Yes" : "No");
|
|
1854
|
+
if (compatible) {
|
|
1855
|
+
success("These proof systems can be composed together");
|
|
1856
|
+
} else {
|
|
1857
|
+
error("These proof systems are not directly compatible");
|
|
1858
|
+
divider();
|
|
1859
|
+
info("Issues:");
|
|
1860
|
+
errors.forEach((e) => {
|
|
1861
|
+
error(` - ${e.name}: ${e.message}`);
|
|
1862
|
+
});
|
|
1863
|
+
}
|
|
1864
|
+
if (warnings.length > 0) {
|
|
1865
|
+
divider();
|
|
1866
|
+
info("Warnings:");
|
|
1867
|
+
warnings.forEach((w) => {
|
|
1868
|
+
info(` \u26A0 ${w.name}: ${w.message}`);
|
|
1869
|
+
});
|
|
1870
|
+
}
|
|
1871
|
+
if (!compatible) {
|
|
1872
|
+
process.exit(1);
|
|
1873
|
+
}
|
|
1874
|
+
} catch (err) {
|
|
1875
|
+
if (format === "json") {
|
|
1876
|
+
json({ error: String(err) });
|
|
1877
|
+
} else {
|
|
1878
|
+
error(`Failed to check compatibility: ${err}`);
|
|
1879
|
+
}
|
|
1880
|
+
process.exit(1);
|
|
1881
|
+
}
|
|
1882
|
+
});
|
|
1883
|
+
return proof;
|
|
1884
|
+
}
|
|
1885
|
+
|
|
1007
1886
|
// src/index.ts
|
|
1008
|
-
var program = new
|
|
1887
|
+
var program = new Command14();
|
|
1009
1888
|
program.name("sip").description("Shielded Intents Protocol (SIP) - Privacy layer for cross-chain transactions").version("0.2.0");
|
|
1010
1889
|
program.addCommand(createSetupCommand());
|
|
1011
1890
|
program.addCommand(createInitCommand());
|
|
@@ -1018,4 +1897,6 @@ program.addCommand(createVerifyCommand());
|
|
|
1018
1897
|
program.addCommand(createQuoteCommand());
|
|
1019
1898
|
program.addCommand(createSwapCommand());
|
|
1020
1899
|
program.addCommand(createScanCommand());
|
|
1900
|
+
program.addCommand(createBackendsCommand());
|
|
1901
|
+
program.addCommand(createProofCommand());
|
|
1021
1902
|
program.parse();
|