enigma-memory 0.1.18 → 0.1.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +76 -24
- package/apps/cli/bin/enigma-desktop.mjs +140 -0
- package/apps/cli/bin/enigma-terminal.mjs +78 -0
- package/apps/cli/bin/enigma.mjs +1923 -285
- package/apps/desktop/electron-main.cjs +217 -0
- package/apps/desktop/package.json +12 -0
- package/apps/desktop/src/app.js +264 -7
- package/apps/desktop/src/index.html +3514 -1373
- package/apps/desktop/src/launch-electron.mjs +51 -0
- package/apps/desktop/src/server.mjs +2914 -0
- package/apps/desktop/src/styles.css +2972 -260
- package/apps/desktop/src/zk-browser-prove.mjs +53 -0
- package/apps/desktop/src/zk-state.mjs +1789 -0
- package/apps/gateway/bin/enigma-gateway.mjs +102 -5
- package/apps/gateway/src/server.mjs +271 -8
- package/apps/ios/EnigmaCore/Package.swift +12 -0
- package/apps/ios/EnigmaCore/Sources/EnigmaCore/EnigmaAPIClient.swift +227 -0
- package/apps/ios/EnigmaCore/Sources/EnigmaCore/Models.swift +278 -0
- package/apps/ios/EnigmaCore/Sources/EnigmaCore/PKCE.swift +96 -0
- package/apps/ios/EnigmaCore/Sources/EnigmaCore/PrivacyMinimizer.swift +187 -0
- package/apps/ios/EnigmaCore/Sources/EnigmaCore/ToolModels.swift +129 -0
- package/apps/ios/EnigmaCore/Tests/EnigmaCoreTests/EnigmaCoreTests.swift +42 -0
- package/apps/ios/EnigmaIOS/Enigma/AppModel.swift +346 -0
- package/apps/ios/EnigmaIOS/Enigma/Assets.xcassets/AccentColor.colorset/Contents.json +12 -0
- package/apps/ios/EnigmaIOS/Enigma/Assets.xcassets/AppIcon.appiconset/Contents.json +11 -0
- package/apps/ios/EnigmaIOS/Enigma/Assets.xcassets/AppIcon.appiconset/EnigmaAppIcon.png +0 -0
- package/apps/ios/EnigmaIOS/Enigma/Assets.xcassets/Contents.json +3 -0
- package/apps/ios/EnigmaIOS/Enigma/Assets.xcassets/LaunchBackground.colorset/Contents.json +12 -0
- package/apps/ios/EnigmaIOS/Enigma/ChatView.swift +181 -0
- package/apps/ios/EnigmaIOS/Enigma/CouncilView.swift +78 -0
- package/apps/ios/EnigmaIOS/Enigma/CreateView.swift +152 -0
- package/apps/ios/EnigmaIOS/Enigma/EnigmaApp.swift +52 -0
- package/apps/ios/EnigmaIOS/Enigma/Info.plist +52 -0
- package/apps/ios/EnigmaIOS/Enigma/NaturalLanguagePrivacyTagger.swift +26 -0
- package/apps/ios/EnigmaIOS/Enigma/OAuthClient.swift +321 -0
- package/apps/ios/EnigmaIOS/Enigma/OnboardingView.swift +105 -0
- package/apps/ios/EnigmaIOS/Enigma/PrivateVaultView.swift +275 -0
- package/apps/ios/EnigmaIOS/Enigma/SecureStore.swift +76 -0
- package/apps/ios/EnigmaIOS/Enigma/SettingsView.swift +60 -0
- package/apps/ios/EnigmaIOS/Enigma/Theme.swift +80 -0
- package/apps/ios/EnigmaIOS/EnigmaIOS.xcodeproj/project.pbxproj +211 -0
- package/apps/ios/EnigmaIOS/EnigmaIOS.xcodeproj/xcshareddata/xcschemes/Enigma.xcscheme +23 -0
- package/apps/native-host/README.md +19 -8
- package/apps/native-host/bin/enigma-native-host.mjs +229 -13
- package/apps/relay/bin/enigma-relay.mjs +103 -5
- package/apps/relay/src/federation-runtime.mjs +618 -0
- package/apps/relay/src/server.mjs +310 -9
- package/apps/verifier/bin/enigma-verify.mjs +327 -11
- package/cortex-v3/circuits/build/intent_vk_bytes.json +35 -0
- package/cortex-v3/circuits/build/sale_vk_bytes.json +35 -0
- package/cortex-v3/circuits/build/vk_bytes.json +32 -0
- package/cortex-v3/proving-assets.json +64 -0
- package/cortex-v3/zk/BUILD-CONTRACT.md +87 -0
- package/cortex-v3/zk/action-transition-vk.json +119 -0
- package/cortex-v3/zk/alias-adversarial.test.mjs +220 -0
- package/cortex-v3/zk/groth16-verify-child.mjs +17 -0
- package/cortex-v3/zk/intent-witness.mjs +365 -0
- package/cortex-v3/zk/intent-witness.test.mjs +485 -0
- package/cortex-v3/zk/proving-assets.mjs +203 -0
- package/cortex-v3/zk/sale-witness.mjs +783 -0
- package/cortex-v3/zk/sale-witness.test.mjs +784 -0
- package/cortex-v3/zk/sealed-sale-release-vk.json +119 -0
- package/cortex-v3/zk/settlement-evidence.mjs +722 -0
- package/cortex-v3/zk/setup-intent.mjs +688 -0
- package/cortex-v3/zk/setup-sale.mjs +666 -0
- package/cortex-v3/zk/setup.mjs +594 -0
- package/cortex-v3/zk/witness.mjs +184 -0
- package/cortex-v3/zk/zk-codec.mjs +232 -0
- package/cortex-v3/zk/zk-codec.test.mjs +293 -0
- package/cortex-v3/zk/zk-settle.mjs +370 -0
- package/cortex-v3/zk/zk-tree.mjs +256 -0
- package/cortex-v3/zk/zk-tree.test.mjs +419 -0
- package/deploy/docker-compose.local-production-simulation.yml +36 -0
- package/docs/browser-extension-install.md +8 -6
- package/docs/client-connectors.md +15 -11
- package/docs/developer-ecosystem.md +15 -13
- package/docs/enigma-memory-ready-conformance.md +11 -9
- package/docs/install-anywhere.md +61 -28
- package/docs/installers-and-desktop.md +8 -7
- package/docs/novelty-invention-candidates.md +161 -161
- package/docs/proof-network-claim-boundaries.md +320 -318
- package/examples/01-quickstart-agent/index.mjs +49 -0
- package/examples/01_agent_memory_quickstart.mjs +57 -0
- package/examples/02-multi-agent-swarm/index.mjs +57 -0
- package/examples/02_cross_model_passport.mjs +64 -0
- package/examples/03-langchain-memory/index.mjs +41 -0
- package/examples/03_poseidon_commitment_verification.mjs +71 -0
- package/examples/04-python-trading-agent/trader.py +49 -0
- package/examples/README.md +27 -0
- package/examples/ci/github-actions.yml +7 -2
- package/package.json +142 -11
- package/packages/adapters/PACKAGE_CONTRACT.md +1 -1
- package/packages/connectors/src/index.js +196 -4
- package/packages/connectors/swarm-router.mjs +168 -0
- package/packages/core/src/index.js +248 -1
- package/packages/core/src/version.mjs +7 -0
- package/packages/dev-tools/package.json +19 -0
- package/packages/dev-tools/src/index.js +4 -0
- package/packages/dev-tools/src/memory-benchmark-suite.js +112 -0
- package/packages/dev-tools/src/swarm-simulator.js +101 -0
- package/packages/dev-tools/src/vault-inspector.js +114 -0
- package/packages/dev-tools/src/vector-benchmark.js +100 -0
- package/packages/developer-platform/src/access-credentials.js +341 -0
- package/packages/developer-platform/src/http.js +132 -0
- package/packages/developer-platform/src/index.js +4 -0
- package/packages/developer-platform/src/usage-http.js +60 -0
- package/packages/developer-platform/src/usage.js +295 -0
- package/packages/enclave-runtime/attestation.mjs +159 -0
- package/packages/enclave-runtime/index.mjs +47 -0
- package/packages/enclave-runtime/session-manager.mjs +253 -0
- package/packages/enclave-runtime/zeroization-proof.mjs +227 -0
- package/packages/enigma-reflex/package.json +14 -0
- package/packages/enigma-reflex/src/index.js +204 -0
- package/packages/enigma-reflex/training/generate-dataset.mjs +40 -0
- package/packages/enigma-reflex/training/requirements.txt +8 -0
- package/packages/enigma-reflex/training/train.py +314 -0
- package/packages/enigma-weave/LICENSE +22 -0
- package/packages/enigma-weave/UPSTREAM.json +21 -0
- package/packages/enigma-weave/package.json +14 -0
- package/packages/enigma-weave/src/index.js +286 -0
- package/packages/hosted-cloud/src/index.js +80 -5
- package/packages/importers/src/index.js +432 -0
- package/packages/inference-runtime/src/browser.js +401 -0
- package/packages/inference-runtime/src/chat.js +265 -0
- package/packages/inference-runtime/src/code.js +407 -0
- package/packages/inference-runtime/src/contracts.js +162 -0
- package/packages/inference-runtime/src/http.js +232 -0
- package/packages/inference-runtime/src/image.js +186 -0
- package/packages/inference-runtime/src/index.js +10 -0
- package/packages/inference-runtime/src/model-router.js +320 -0
- package/packages/inference-runtime/src/platform.js +125 -0
- package/packages/inference-runtime/src/privacy.js +400 -0
- package/packages/inference-runtime/src/video.js +253 -0
- package/packages/mcp-server/README.md +22 -6
- package/packages/mcp-server/bin/enigma-mcp.mjs +2 -1
- package/packages/mcp-server/src/index.js +1418 -105
- package/packages/mcp-server/src/oauth.js +561 -0
- package/packages/mcp-server/src/private-handoff.js +84 -0
- package/packages/mcp-server/src/remote-http.js +273 -0
- package/packages/mcp-server/src/remote-policy.js +72 -0
- package/packages/mcp-server/swarm-bridge.mjs +361 -0
- package/packages/mesh/index.d.ts +283 -0
- package/packages/mesh/package.json +23 -0
- package/packages/mesh/src/crypto.js +189 -0
- package/packages/mesh/src/federation-packets.js +353 -0
- package/packages/mesh/src/gossip.js +311 -0
- package/packages/mesh/src/index.js +6 -0
- package/packages/mesh/src/protocol.js +255 -0
- package/packages/mesh/src/router.js +279 -0
- package/packages/mesh/src/transport.js +306 -0
- package/packages/passport/src/index.js +426 -1
- package/packages/private-economy/src/credits-http.js +100 -0
- package/packages/private-economy/src/credits.js +447 -0
- package/packages/private-economy/src/index.js +5 -0
- package/packages/private-economy/src/payments-http.js +120 -0
- package/packages/private-economy/src/payments.js +509 -0
- package/packages/private-economy/src/x402.js +346 -0
- package/packages/proof-network/PACKAGE_CONTRACT.md +21 -0
- package/packages/rag/index.d.ts +182 -0
- package/packages/rag/models/Xenova/all-MiniLM-L6-v2/THIRD_PARTY_LICENSES.txt +207 -0
- package/packages/rag/models/Xenova/all-MiniLM-L6-v2/config.json +25 -0
- package/packages/rag/models/Xenova/all-MiniLM-L6-v2/onnx/model_quantized.onnx +0 -0
- package/packages/rag/models/Xenova/all-MiniLM-L6-v2/sha256-manifest.json +28 -0
- package/packages/rag/models/Xenova/all-MiniLM-L6-v2/tokenizer.json +30686 -0
- package/packages/rag/models/Xenova/all-MiniLM-L6-v2/tokenizer_config.json +15 -0
- package/packages/rag/package.json +27 -0
- package/packages/rag/src/blinded-search.js +109 -0
- package/packages/rag/src/bm25.js +169 -0
- package/packages/rag/src/embeddings.js +459 -0
- package/packages/rag/src/hybrid.js +76 -0
- package/packages/rag/src/index.js +38 -0
- package/packages/rag/src/reranker.js +61 -0
- package/packages/rag/src/research.js +107 -0
- package/packages/rag/src/vector-store.js +430 -0
- package/packages/rag/src/verify-model-artifacts.mjs +4 -0
- package/packages/sdk/index.d.ts +760 -0
- package/packages/sdk/package.json +33 -0
- package/packages/sdk/python/README.md +24 -0
- package/packages/sdk/python/enigma_sdk.py +250 -0
- package/packages/sdk/python/pyproject.toml +34 -0
- package/packages/sdk/python/requirements.txt +1 -0
- package/packages/sdk/python/setup.py +20 -0
- package/packages/sdk/src/federation/capability-grant.js +389 -0
- package/packages/sdk/src/federation/federation-router.js +360 -0
- package/packages/sdk/src/federation/ghostmesh-bridge.js +497 -0
- package/packages/sdk/src/federation/index.js +3 -0
- package/packages/sdk/src/index.js +1796 -0
- package/packages/sdk/src/intelligence/contradiction.js +337 -0
- package/packages/sdk/src/intelligence/decision-engine.js +155 -0
- package/packages/sdk/src/intelligence/index.js +4 -0
- package/packages/sdk/src/intelligence/ontology.js +122 -0
- package/packages/sdk/src/intelligence/temporal.js +123 -0
- package/packages/sdk/src/market-client.js +142 -0
- package/packages/sdk/src/mesh-client.js +110 -0
- package/packages/sdk/src/middleware/index.js +3 -0
- package/packages/sdk/src/middleware/langchain.js +159 -0
- package/packages/sdk/src/middleware/llamaindex.js +101 -0
- package/packages/sdk/src/middleware/vercel-ai.js +112 -0
- package/packages/sdk/src/rag-client.js +85 -0
- package/packages/sdk/src/swarm-orchestrator.js +260 -0
- package/packages/settlement/PACKAGE_CONTRACT.md +1 -1
- package/packages/snapcompact/THIRD_PARTY_LICENSES.txt +40 -0
- package/packages/snapcompact/assets/8x13-latin1.bdf +3837 -0
- package/packages/snapcompact/index.d.ts +284 -0
- package/packages/snapcompact/package.json +25 -0
- package/packages/snapcompact/src/index.js +716 -0
- package/packages/storage/PACKAGE_CONTRACT.md +1 -1
- package/packages/terminal-console/animations.mjs +240 -0
- package/packages/terminal-console/auto-anchor.mjs +220 -0
- package/packages/terminal-console/banner.mjs +91 -0
- package/packages/terminal-console/commands.mjs +459 -0
- package/packages/terminal-console/delegation.mjs +152 -0
- package/packages/terminal-console/index.mjs +5 -0
- package/packages/terminal-console/outbox.mjs +143 -0
- package/packages/terminal-console/phantom-bridge.mjs +637 -0
- package/packages/terminal-console/repl.mjs +136 -0
- package/packages/terminal-console/signer-store.mjs +130 -0
- package/packages/terminal-console/solana-rpc.mjs +214 -0
- package/packages/terminal-console/solana-transport.mjs +189 -0
- package/packages/terminal-tui/dashboard.mjs +214 -0
- package/packages/terminal-tui/index.mjs +28 -0
- package/packages/terminal-tui/merkle-tree-renderer.mjs +268 -0
- package/packages/terminal-tui/telemetry-hud.mjs +137 -0
- package/packages/vault/index.d.ts +449 -0
- package/packages/vault/package.json +27 -0
- package/packages/vault/src/e2ee.mjs +393 -0
- package/packages/vault/src/enclave.js +481 -0
- package/packages/vault/src/erasure.js +207 -0
- package/packages/vault/src/index.js +1018 -155
- package/packages/vault/src/persistence.js +307 -0
- package/packages/vault/src/poseidon.js +354 -0
- package/packages/vault/src/receipt.js +459 -0
- package/scripts/benchmark-optical-context.mjs +166 -0
- package/scripts/bootstrap-enigma.mjs +502 -0
- package/scripts/build-edge-backend-workers.mjs +20 -5
- package/scripts/build-goal-completion-audit.mjs +72 -25
- package/scripts/build-hosted-api-key-lifecycle.mjs +26 -8
- package/scripts/build-hosted-customer-lifecycle.mjs +20 -3
- package/scripts/build-hosted-probe-worker.mjs +19 -4
- package/scripts/build-installer-assets.mjs +41 -21
- package/scripts/build-operator-evidence-starter.mjs +59 -1
- package/scripts/build-production-backend-env-kit.mjs +2 -0
- package/scripts/build-production-unblocker.mjs +3 -0
- package/scripts/check.mjs +17 -3
- package/scripts/collect-hosted-backend-live-evidence.mjs +49 -12
- package/scripts/install-enigma-local.mjs +18 -5
- package/scripts/release-audit.mjs +65 -109
- package/scripts/release-provenance.mjs +6 -0
- package/scripts/run-backend-readiness-smoke.mjs +112 -10
- package/scripts/scan-secrets.mjs +1 -0
- package/scripts/simulate-production-env.mjs +7 -2
- package/scripts/validate-hosted-backend-live.mjs +112 -1
- package/specs/antibody-pack-v1.schema.json +95 -0
- package/specs/antigen-envelope-v1.schema.json +81 -0
- package/specs/boundary-manifest-v1.schema.json +35 -35
- package/specs/capsule-v1.schema.json +55 -55
- package/specs/claim-boundary-manifest-v1.schema.json +22 -22
- package/specs/claim-ledger-v1.schema.json +291 -0
- package/specs/context-passport-v1.schema.json +59 -0
- package/specs/deletion-tombstone-v1.schema.json +26 -26
- package/specs/evidence-packet-v1.schema.json +177 -0
- package/specs/hosted-backend-live-evidence-v1.schema.json +72 -3
- package/specs/immune-scan-report-v1.schema.json +112 -0
- package/specs/lifecycle-receipt-log-v1.schema.json +67 -0
- package/specs/memory-atom-v1.schema.json +59 -0
- package/specs/memory-event-v1.schema.json +42 -42
- package/specs/passport-v1.schema.json +50 -50
- package/specs/proof-of-non-use-v1.schema.json +65 -0
- package/specs/quarantine-record-v1.schema.json +126 -0
- package/specs/receipt-v1.schema.json +61 -61
- package/specs/state-checkpoint-v1.schema.json +37 -37
- package/specs/trust-bundle-v1.schema.json +56 -56
- package/specs/trust-card-v1.schema.json +119 -0
- package/docs/proof-network-launch-plan.md +0 -421
- package/packages/metering/PACKAGE_CONTRACT.md +0 -20
- package/scripts/build-ai-orchestration-plan.mjs +0 -248
|
@@ -0,0 +1,485 @@
|
|
|
1
|
+
// intent-witness.test.mjs — tests for zk/intent-witness.mjs, the client-side
|
|
2
|
+
// witness builder for circuits/intent_attestation.circom (IntentAttestation).
|
|
3
|
+
//
|
|
4
|
+
// Legs:
|
|
5
|
+
// 1. actionHash KAT — fixed x402 descriptor → pinned decimal digest, plus an
|
|
6
|
+
// independent recomputation (raw circomlibjs + Buffer LE limbs).
|
|
7
|
+
// 2. descriptor limb derivation — base58 decode, LE split, each < 2^128.
|
|
8
|
+
// 3. payloadCommit / intent nullifier — independent recomputation.
|
|
9
|
+
// 4. AllowlistTree (D=16) — membership proof verifies, foreign leaf fails.
|
|
10
|
+
// 5. buildIntentWitness — full valid witness shape, exact signal names,
|
|
11
|
+
// public-signal order; client-side rejections (over-cap amount,
|
|
12
|
+
// non-allowlisted recipient, time lock) throw.
|
|
13
|
+
// 6. Signal-name reconciliation vs circuits/intent_attestation.circom
|
|
14
|
+
// (when the file exists): every `signal input` name must match exactly.
|
|
15
|
+
// 7. WASM-gated (polls up to 20 min for IntentCircuitBuild artifacts;
|
|
16
|
+
// override with INTENT_WITNESS_POLL_MS=0 to skip immediately):
|
|
17
|
+
// valid witness satisfies the compiled circuit; tampered witnesses
|
|
18
|
+
// (amount > maxAmount, validAfterSlot > currentSlot, non-allowlisted
|
|
19
|
+
// payee limbs) are rejected by the witness calculator.
|
|
20
|
+
//
|
|
21
|
+
// Run: node --test cortex-v3/zk/intent-witness.test.mjs
|
|
22
|
+
|
|
23
|
+
import test from "node:test";
|
|
24
|
+
import assert from "node:assert/strict";
|
|
25
|
+
import { execFileSync } from "node:child_process";
|
|
26
|
+
import { existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
27
|
+
import { createRequire } from "node:module";
|
|
28
|
+
import { tmpdir } from "node:os";
|
|
29
|
+
import { dirname, join, resolve } from "node:path";
|
|
30
|
+
import { fileURLToPath } from "node:url";
|
|
31
|
+
|
|
32
|
+
import { buildPoseidon } from "circomlibjs";
|
|
33
|
+
import bs58 from "bs58";
|
|
34
|
+
|
|
35
|
+
import { NoteTree, verifyMerkleProof } from "./zk-tree.mjs";
|
|
36
|
+
import {
|
|
37
|
+
ALLOW_DEPTH,
|
|
38
|
+
AllowlistTree,
|
|
39
|
+
DOMAIN_ACTION,
|
|
40
|
+
DOMAIN_ALLOW,
|
|
41
|
+
DOMAIN_INTENT_NF,
|
|
42
|
+
DOMAIN_POLICY,
|
|
43
|
+
INTENT_PUBLIC_SIGNALS,
|
|
44
|
+
NOTE_DEPTH,
|
|
45
|
+
buildIntentWitness,
|
|
46
|
+
computeActionHash,
|
|
47
|
+
computeIntentNote,
|
|
48
|
+
computeIntentNullifier,
|
|
49
|
+
computePayloadCommit,
|
|
50
|
+
} from "./intent-witness.mjs";
|
|
51
|
+
|
|
52
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
53
|
+
const PKG_ROOT = resolve(__dirname, "..");
|
|
54
|
+
const BUILD = join(PKG_ROOT, "circuits", "build");
|
|
55
|
+
const CIRCUIT_SRC = join(PKG_ROOT, "circuits", "intent_attestation.circom");
|
|
56
|
+
const R1CS = join(BUILD, "intent_attestation.r1cs");
|
|
57
|
+
const WASM = join(BUILD, "intent_attestation_js", "intent_attestation.wasm");
|
|
58
|
+
const WITNESS_CALC = join(BUILD, "intent_attestation_js", "witness_calculator.js");
|
|
59
|
+
|
|
60
|
+
const require = createRequire(import.meta.url);
|
|
61
|
+
|
|
62
|
+
// ---------------------------------------------------------------------------
|
|
63
|
+
// Fixed test fixture
|
|
64
|
+
// ---------------------------------------------------------------------------
|
|
65
|
+
const RECIPIENT = "So11111111111111111111111111111111111111112"; // wSOL mint addr as payee
|
|
66
|
+
const MINT = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"; // USDC mint
|
|
67
|
+
const AMOUNT = 1_000_000_000n; // 1 SOL in lamports
|
|
68
|
+
const NONCE = Uint8Array.from({ length: 16 }, (_, i) => i + 1);
|
|
69
|
+
const OWNER_KEY = 12345678901234567890123456789012345678901234567890n;
|
|
70
|
+
const RHO = 98765432109876543210987654321098765432109876543210n;
|
|
71
|
+
const MAX_AMOUNT = 2_000_000_000n;
|
|
72
|
+
const VALID_AFTER = 100n;
|
|
73
|
+
const CURRENT_SLOT = 150n;
|
|
74
|
+
|
|
75
|
+
// KAT pinned by an independent computation (raw circomlibjs poseidon over
|
|
76
|
+
// limbs derived with Buffer LE reduction — see the independent leg below).
|
|
77
|
+
const KAT = Object.freeze({
|
|
78
|
+
rLo: 71446497128034137124372738219197373190n,
|
|
79
|
+
rHi: 1329227996075816458295437426742117594n,
|
|
80
|
+
mintLo: 65738568574496699805876393164525992646n,
|
|
81
|
+
mintHi: 129418957939906822851699979667271629745n,
|
|
82
|
+
nonce: 21345817372864405881847059188222722561n,
|
|
83
|
+
actionHash: 11192328813738960605611739244535981126494623159630851179122912860248433917861n,
|
|
84
|
+
payeeLeaf: 7211849756294928538748674744383553675446290308714877925270988920289318996875n,
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
const poseidonC = await buildPoseidon();
|
|
88
|
+
const F = poseidonC.F;
|
|
89
|
+
const independentPoseidon = (xs) => F.toObject(poseidonC(xs.map((x) => F.e(BigInt(x)))));
|
|
90
|
+
|
|
91
|
+
/** Independent LE reducer (Buffer-based, no shared code with the module). */
|
|
92
|
+
function leBytes(buf) {
|
|
93
|
+
let v = 0n;
|
|
94
|
+
for (let i = buf.length - 1; i >= 0; i--) v = v * 256n + BigInt(buf[i]);
|
|
95
|
+
return v;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** Build the standard valid fixture: note tree + allowlist + witness args. */
|
|
99
|
+
function makeFixture() {
|
|
100
|
+
const allowlist = new AllowlistTree();
|
|
101
|
+
allowlist.insert(RECIPIENT);
|
|
102
|
+
const noteTree = new NoteTree();
|
|
103
|
+
noteTree.insert(111n); // filler note at index 0
|
|
104
|
+
const payloadCommit = computePayloadCommit({
|
|
105
|
+
maxAmount: MAX_AMOUNT,
|
|
106
|
+
allowlistRoot: allowlist.root(),
|
|
107
|
+
validAfterSlot: VALID_AFTER,
|
|
108
|
+
});
|
|
109
|
+
noteTree.insert(computeIntentNote(OWNER_KEY, RHO, payloadCommit)); // index 1
|
|
110
|
+
return {
|
|
111
|
+
noteTree,
|
|
112
|
+
allowlist,
|
|
113
|
+
args: {
|
|
114
|
+
noteTree,
|
|
115
|
+
noteIndex: 1,
|
|
116
|
+
ownerKey: OWNER_KEY,
|
|
117
|
+
rho: RHO,
|
|
118
|
+
maxAmount: MAX_AMOUNT,
|
|
119
|
+
allowlist,
|
|
120
|
+
validAfterSlot: VALID_AFTER,
|
|
121
|
+
recipient: RECIPIENT,
|
|
122
|
+
mint: MINT,
|
|
123
|
+
amountLamports: AMOUNT,
|
|
124
|
+
nonceBytes: NONCE,
|
|
125
|
+
currentSlot: CURRENT_SLOT,
|
|
126
|
+
},
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const EXPECTED_SIGNALS = Object.freeze([
|
|
131
|
+
...INTENT_PUBLIC_SIGNALS,
|
|
132
|
+
"ownerKey",
|
|
133
|
+
"rho",
|
|
134
|
+
"maxAmount",
|
|
135
|
+
"allowlistRoot",
|
|
136
|
+
"validAfterSlot",
|
|
137
|
+
"notePathElements",
|
|
138
|
+
"notePathIndices",
|
|
139
|
+
"rLo",
|
|
140
|
+
"rHi",
|
|
141
|
+
"mintLo",
|
|
142
|
+
"mintHi",
|
|
143
|
+
"amount",
|
|
144
|
+
"nonce",
|
|
145
|
+
"allowPathElements",
|
|
146
|
+
"allowPathIndices",
|
|
147
|
+
]);
|
|
148
|
+
|
|
149
|
+
// ---------------------------------------------------------------------------
|
|
150
|
+
// 1–2. actionHash KAT + limb derivation
|
|
151
|
+
// ---------------------------------------------------------------------------
|
|
152
|
+
test("computeActionHash matches the pinned KAT and independent recomputation", () => {
|
|
153
|
+
const out = computeActionHash({
|
|
154
|
+
recipientPubkeyBase58: RECIPIENT,
|
|
155
|
+
mintBase58: MINT,
|
|
156
|
+
amountLamports: AMOUNT,
|
|
157
|
+
nonceBytes: NONCE,
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
// pinned known-answer values
|
|
161
|
+
assert.equal(out.rLo, KAT.rLo);
|
|
162
|
+
assert.equal(out.rHi, KAT.rHi);
|
|
163
|
+
assert.equal(out.mintLo, KAT.mintLo);
|
|
164
|
+
assert.equal(out.mintHi, KAT.mintHi);
|
|
165
|
+
assert.equal(out.amount, AMOUNT);
|
|
166
|
+
assert.equal(out.nonce, KAT.nonce);
|
|
167
|
+
assert.equal(out.actionHash, KAT.actionHash, "actionHash KAT drifted");
|
|
168
|
+
|
|
169
|
+
// fully independent recomputation (no module code)
|
|
170
|
+
const r = bs58.decode(RECIPIENT);
|
|
171
|
+
const m = bs58.decode(MINT);
|
|
172
|
+
assert.equal(r.length, 32);
|
|
173
|
+
assert.equal(m.length, 32);
|
|
174
|
+
const limbs = [
|
|
175
|
+
leBytes(r.subarray(0, 16)),
|
|
176
|
+
leBytes(r.subarray(16, 32)),
|
|
177
|
+
leBytes(m.subarray(0, 16)),
|
|
178
|
+
leBytes(m.subarray(16, 32)),
|
|
179
|
+
AMOUNT,
|
|
180
|
+
leBytes(NONCE),
|
|
181
|
+
];
|
|
182
|
+
const TWO_128 = 1n << 128n;
|
|
183
|
+
for (const limb of limbs) {
|
|
184
|
+
assert.ok(limb >= 0n && limb < TWO_128, `limb ${limb} outside [0, 2^128)`);
|
|
185
|
+
}
|
|
186
|
+
assert.equal(out.rLo, limbs[0]);
|
|
187
|
+
assert.equal(out.rHi, limbs[1]);
|
|
188
|
+
assert.equal(out.mintLo, limbs[2]);
|
|
189
|
+
assert.equal(out.mintHi, limbs[3]);
|
|
190
|
+
assert.equal(out.nonce, limbs[5]);
|
|
191
|
+
assert.equal(
|
|
192
|
+
out.actionHash,
|
|
193
|
+
independentPoseidon([DOMAIN_ACTION, ...limbs]),
|
|
194
|
+
"independent Poseidon7 recomputation disagrees",
|
|
195
|
+
);
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
test("computeActionHash rejects malformed descriptors", () => {
|
|
199
|
+
assert.throws(
|
|
200
|
+
() =>
|
|
201
|
+
computeActionHash({
|
|
202
|
+
recipientPubkeyBase58: "1111", // 3 bytes, not 32
|
|
203
|
+
mintBase58: MINT,
|
|
204
|
+
amountLamports: AMOUNT,
|
|
205
|
+
nonceBytes: NONCE,
|
|
206
|
+
}),
|
|
207
|
+
/32 bytes/,
|
|
208
|
+
);
|
|
209
|
+
assert.throws(
|
|
210
|
+
() =>
|
|
211
|
+
computeActionHash({
|
|
212
|
+
recipientPubkeyBase58: RECIPIENT,
|
|
213
|
+
mintBase58: MINT,
|
|
214
|
+
amountLamports: 1n << 64n, // u64 overflow
|
|
215
|
+
nonceBytes: NONCE,
|
|
216
|
+
}),
|
|
217
|
+
/u64/,
|
|
218
|
+
);
|
|
219
|
+
assert.throws(
|
|
220
|
+
() =>
|
|
221
|
+
computeActionHash({
|
|
222
|
+
recipientPubkeyBase58: RECIPIENT,
|
|
223
|
+
mintBase58: MINT,
|
|
224
|
+
amountLamports: AMOUNT,
|
|
225
|
+
nonceBytes: new Uint8Array(8), // wrong nonce length
|
|
226
|
+
}),
|
|
227
|
+
/16 bytes/,
|
|
228
|
+
);
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
// ---------------------------------------------------------------------------
|
|
232
|
+
// 3. payloadCommit + nullifier
|
|
233
|
+
// ---------------------------------------------------------------------------
|
|
234
|
+
test("computePayloadCommit and computeIntentNullifier match independent recomputation", () => {
|
|
235
|
+
const allowlistRoot = 42424242424242424242424242424242424242424242424242n;
|
|
236
|
+
const pc = computePayloadCommit({
|
|
237
|
+
maxAmount: MAX_AMOUNT,
|
|
238
|
+
allowlistRoot,
|
|
239
|
+
validAfterSlot: VALID_AFTER,
|
|
240
|
+
});
|
|
241
|
+
assert.equal(
|
|
242
|
+
pc,
|
|
243
|
+
independentPoseidon([DOMAIN_POLICY, MAX_AMOUNT, allowlistRoot, VALID_AFTER]),
|
|
244
|
+
);
|
|
245
|
+
|
|
246
|
+
const nf = computeIntentNullifier(OWNER_KEY, KAT.actionHash);
|
|
247
|
+
assert.equal(nf, independentPoseidon([DOMAIN_INTENT_NF, OWNER_KEY, KAT.actionHash]));
|
|
248
|
+
|
|
249
|
+
const note = computeIntentNote(OWNER_KEY, RHO, pc);
|
|
250
|
+
assert.equal(note, independentPoseidon([OWNER_KEY, RHO, pc]));
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
// ---------------------------------------------------------------------------
|
|
254
|
+
// 4. AllowlistTree
|
|
255
|
+
// ---------------------------------------------------------------------------
|
|
256
|
+
test("AllowlistTree depth-16 membership proofs verify; foreign leaves fail", () => {
|
|
257
|
+
const tree = new AllowlistTree();
|
|
258
|
+
assert.equal(tree.depth, ALLOW_DEPTH);
|
|
259
|
+
const ins = tree.insert(RECIPIENT);
|
|
260
|
+
assert.equal(ins.index, 0);
|
|
261
|
+
assert.equal(ins.leaf, KAT.payeeLeaf, "payee leaf KAT drifted");
|
|
262
|
+
assert.equal(ins.leaf, independentPoseidon([DOMAIN_ALLOW, KAT.rLo, KAT.rHi]));
|
|
263
|
+
assert.equal(ins.pathElements.length, ALLOW_DEPTH);
|
|
264
|
+
assert.equal(ins.pathIndices.length, ALLOW_DEPTH);
|
|
265
|
+
|
|
266
|
+
assert.ok(tree.verifyProof(ins.leaf, ins.pathElements, ins.pathIndices), "own proof must verify");
|
|
267
|
+
assert.ok(
|
|
268
|
+
verifyMerkleProof(ins.leaf, ins.pathElements, ins.pathIndices, tree.root(), ALLOW_DEPTH),
|
|
269
|
+
"zk-tree verifyMerkleProof(depth=16) must verify the allowlist proof",
|
|
270
|
+
);
|
|
271
|
+
|
|
272
|
+
// a second payee; both proofs still verify against the updated root
|
|
273
|
+
const other = "11111111111111111111111111111111"; // system program (32 zero bytes)
|
|
274
|
+
const ins2 = tree.insert(other);
|
|
275
|
+
assert.equal(ins2.index, 1);
|
|
276
|
+
assert.ok(tree.verifyProof(ins.leaf, tree.getProof(0).pathElements, tree.getProof(0).pathIndices));
|
|
277
|
+
assert.ok(tree.verifyProof(ins2.leaf, ins2.pathElements, ins2.pathIndices));
|
|
278
|
+
|
|
279
|
+
// foreign leaf must not verify
|
|
280
|
+
const foreign = independentPoseidon([DOMAIN_ALLOW, 1n, 2n]);
|
|
281
|
+
assert.ok(!tree.verifyProof(foreign, ins2.pathElements, ins2.pathIndices));
|
|
282
|
+
assert.equal(tree.indexOf(1n, 2n), -1);
|
|
283
|
+
assert.equal(tree.indexOf(KAT.rLo, KAT.rHi), 0);
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
// ---------------------------------------------------------------------------
|
|
287
|
+
// 5. buildIntentWitness
|
|
288
|
+
// ---------------------------------------------------------------------------
|
|
289
|
+
test("buildIntentWitness emits exact signal names, decimal strings, public order", () => {
|
|
290
|
+
const { args, noteTree, allowlist } = makeFixture();
|
|
291
|
+
const w = buildIntentWitness(args);
|
|
292
|
+
|
|
293
|
+
assert.deepEqual(
|
|
294
|
+
Object.keys(w).sort(),
|
|
295
|
+
[...EXPECTED_SIGNALS].sort(),
|
|
296
|
+
"witness signal-name set drifted from the circuit spec",
|
|
297
|
+
);
|
|
298
|
+
for (const [k, v] of Object.entries(w)) {
|
|
299
|
+
if (Array.isArray(v)) {
|
|
300
|
+
for (const x of v) assert.match(x, /^\d+$/, `${k} element not a decimal string`);
|
|
301
|
+
} else {
|
|
302
|
+
assert.match(v, /^\d+$/, `${k} not a decimal string`);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
assert.equal(w.notePathElements.length, NOTE_DEPTH);
|
|
307
|
+
assert.equal(w.notePathIndices.length, NOTE_DEPTH);
|
|
308
|
+
assert.equal(w.allowPathElements.length, ALLOW_DEPTH);
|
|
309
|
+
assert.equal(w.allowPathIndices.length, ALLOW_DEPTH);
|
|
310
|
+
|
|
311
|
+
// public values
|
|
312
|
+
assert.equal(w.noteRoot, noteTree.root().toString());
|
|
313
|
+
assert.equal(w.actionHash, KAT.actionHash.toString());
|
|
314
|
+
assert.equal(w.nullifier, computeIntentNullifier(OWNER_KEY, KAT.actionHash).toString());
|
|
315
|
+
assert.equal(w.currentSlot, CURRENT_SLOT.toString());
|
|
316
|
+
assert.deepEqual(
|
|
317
|
+
INTENT_PUBLIC_SIGNALS,
|
|
318
|
+
["noteRoot", "actionHash", "nullifier", "currentSlot"],
|
|
319
|
+
"public signal order must be [noteRoot, actionHash, nullifier, currentSlot]",
|
|
320
|
+
);
|
|
321
|
+
|
|
322
|
+
// private consistency
|
|
323
|
+
assert.equal(w.allowlistRoot, allowlist.root().toString());
|
|
324
|
+
assert.equal(w.rLo, KAT.rLo.toString());
|
|
325
|
+
assert.equal(w.rHi, KAT.rHi.toString());
|
|
326
|
+
assert.equal(w.mintLo, KAT.mintLo.toString());
|
|
327
|
+
assert.equal(w.mintHi, KAT.mintHi.toString());
|
|
328
|
+
assert.equal(w.amount, AMOUNT.toString());
|
|
329
|
+
assert.equal(w.nonce, KAT.nonce.toString());
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
test("buildIntentWitness rejects policy-violating intents client-side", () => {
|
|
333
|
+
const { args } = makeFixture();
|
|
334
|
+
|
|
335
|
+
// amount over the policy cap
|
|
336
|
+
assert.throws(
|
|
337
|
+
() => buildIntentWitness({ ...args, amountLamports: MAX_AMOUNT + 1n }),
|
|
338
|
+
/exceeds policy maxAmount/,
|
|
339
|
+
);
|
|
340
|
+
// time lock not yet reached
|
|
341
|
+
assert.throws(
|
|
342
|
+
() => buildIntentWitness({ ...args, currentSlot: VALID_AFTER - 1n }),
|
|
343
|
+
/not yet valid/,
|
|
344
|
+
);
|
|
345
|
+
// non-allowlisted recipient
|
|
346
|
+
assert.throws(
|
|
347
|
+
() => buildIntentWitness({ ...args, recipient: "11111111111111111111111111111111" }),
|
|
348
|
+
/not in the allowlist/,
|
|
349
|
+
);
|
|
350
|
+
// note opening does not match the leaf at noteIndex
|
|
351
|
+
assert.throws(
|
|
352
|
+
() => buildIntentWitness({ ...args, rho: RHO + 1n }),
|
|
353
|
+
/does not match the leaf/,
|
|
354
|
+
);
|
|
355
|
+
// stale noteIndex
|
|
356
|
+
assert.throws(() => buildIntentWitness({ ...args, noteIndex: 7 }), /not present/);
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
// ---------------------------------------------------------------------------
|
|
360
|
+
// 6. Signal-name reconciliation vs the .circom source (when present)
|
|
361
|
+
// ---------------------------------------------------------------------------
|
|
362
|
+
test("signal names reconcile with circuits/intent_attestation.circom", (t) => {
|
|
363
|
+
if (!existsSync(CIRCUIT_SRC)) {
|
|
364
|
+
t.skip("circuits/intent_attestation.circom not present yet (IntentCircuitBuild in flight)");
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
const src = readFileSync(CIRCUIT_SRC, "utf8").replace(/\/\/[^\n]*/g, "");
|
|
368
|
+
// Scope to the main template body: helper templates (DualMux/MerkleRoot/
|
|
369
|
+
// PoseidonN) declare their own generic `signal input`s that are not part
|
|
370
|
+
// of the witness contract.
|
|
371
|
+
const tplStart = src.indexOf("template IntentAttestation");
|
|
372
|
+
const tplEnd = src.indexOf("component main");
|
|
373
|
+
assert.ok(tplStart !== -1 && tplEnd > tplStart, "IntentAttestation template not found");
|
|
374
|
+
const tpl = src.slice(tplStart, tplEnd);
|
|
375
|
+
const declared = new Set();
|
|
376
|
+
for (const m of tpl.matchAll(/signal\s+input\s+(\w+)/g)) declared.add(m[1]);
|
|
377
|
+
assert.ok(declared.size > 0, "no `signal input` declarations found in the circuit");
|
|
378
|
+
assert.deepEqual(
|
|
379
|
+
[...declared].sort(),
|
|
380
|
+
[...EXPECTED_SIGNALS].sort(),
|
|
381
|
+
"circuit signal inputs diverge from intent-witness.mjs emission",
|
|
382
|
+
);
|
|
383
|
+
// public order in component main
|
|
384
|
+
const mainMatch = src.match(/component\s+main\s*\{[^}]*public\s*\[([^\]]*)\]/s);
|
|
385
|
+
assert.ok(mainMatch, "component main {public [...]} not found");
|
|
386
|
+
const pub = mainMatch[1].split(",").map((s) => s.trim());
|
|
387
|
+
assert.deepEqual(pub, [...INTENT_PUBLIC_SIGNALS], "public input order diverges");
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
// ---------------------------------------------------------------------------
|
|
391
|
+
// 7. WASM-gated circuit satisfaction legs
|
|
392
|
+
// ---------------------------------------------------------------------------
|
|
393
|
+
const POLL_MS = Number(process.env.INTENT_WITNESS_POLL_MS ?? 20 * 60 * 1000);
|
|
394
|
+
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
395
|
+
|
|
396
|
+
let wasmReady = existsSync(WASM) && existsSync(WITNESS_CALC);
|
|
397
|
+
if (!wasmReady && POLL_MS > 0) {
|
|
398
|
+
const deadline = Date.now() + POLL_MS;
|
|
399
|
+
while (Date.now() < deadline) {
|
|
400
|
+
await sleep(15_000);
|
|
401
|
+
if (existsSync(WASM) && existsSync(WITNESS_CALC)) {
|
|
402
|
+
wasmReady = true;
|
|
403
|
+
break;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
if (!wasmReady) {
|
|
408
|
+
console.log(
|
|
409
|
+
`[intent-witness] intent_attestation wasm artifacts absent after polling ` +
|
|
410
|
+
`(${WASM}); circuit-satisfaction legs will SKIP.`,
|
|
411
|
+
);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
/** `snarkjs wtns check` → true if the witness satisfies the R1CS. */
|
|
415
|
+
function wtnsCheck(wtnsPath) {
|
|
416
|
+
try {
|
|
417
|
+
execFileSync("npx", ["snarkjs", "wtns", "check", R1CS, wtnsPath], {
|
|
418
|
+
shell: true,
|
|
419
|
+
stdio: "pipe",
|
|
420
|
+
cwd: PKG_ROOT,
|
|
421
|
+
});
|
|
422
|
+
return true;
|
|
423
|
+
} catch {
|
|
424
|
+
return false;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
test("compiled circuit accepts a valid intent witness", async (t) => {
|
|
429
|
+
if (!wasmReady) {
|
|
430
|
+
t.skip("intent_attestation wasm absent (see log above); nothing to check against");
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
433
|
+
const tmp = mkdtempSync(join(tmpdir(), "enigma-intent-"));
|
|
434
|
+
t.after(() => rmSync(tmp, { recursive: true, force: true }));
|
|
435
|
+
|
|
436
|
+
const { args } = makeFixture();
|
|
437
|
+
const input = buildIntentWitness(args);
|
|
438
|
+
|
|
439
|
+
const buildCalc = require(WITNESS_CALC);
|
|
440
|
+
const calc = await buildCalc(readFileSync(WASM));
|
|
441
|
+
// sanityCheck=true: the calculator enforces every constraint (asserts).
|
|
442
|
+
const bin = Buffer.from(await calc.calculateWTNSBin(input, true));
|
|
443
|
+
const wtnsPath = join(tmp, "intent.wtns");
|
|
444
|
+
writeFileSync(wtnsPath, bin);
|
|
445
|
+
|
|
446
|
+
if (existsSync(R1CS)) {
|
|
447
|
+
assert.ok(wtnsCheck(wtnsPath), "valid intent witness must satisfy the R1CS");
|
|
448
|
+
}
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
test("compiled circuit rejects policy-violating witnesses", async (t) => {
|
|
452
|
+
if (!wasmReady) {
|
|
453
|
+
t.skip("intent_attestation wasm absent (see log above); nothing to check against");
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
const { args } = makeFixture();
|
|
457
|
+
const valid = buildIntentWitness(args);
|
|
458
|
+
const buildCalc = require(WITNESS_CALC);
|
|
459
|
+
const calc = await buildCalc(readFileSync(WASM));
|
|
460
|
+
|
|
461
|
+
// amount above the policy cap (bypasses the client-side guard by tampering
|
|
462
|
+
// the emitted input — exactly what a cheating prover would try).
|
|
463
|
+
const overCap = { ...valid, amount: (MAX_AMOUNT + 1n).toString() };
|
|
464
|
+
await assert.rejects(
|
|
465
|
+
() => calc.calculateWTNSBin(overCap, true),
|
|
466
|
+
/Assert Failed|Error/,
|
|
467
|
+
"circuit must reject amount > maxAmount",
|
|
468
|
+
);
|
|
469
|
+
|
|
470
|
+
// time lock violated: validAfterSlot after currentSlot.
|
|
471
|
+
const tooEarly = { ...valid, validAfterSlot: (CURRENT_SLOT + 1n).toString() };
|
|
472
|
+
await assert.rejects(
|
|
473
|
+
() => calc.calculateWTNSBin(tooEarly, true),
|
|
474
|
+
/Assert Failed|Error/,
|
|
475
|
+
"circuit must reject validAfterSlot > currentSlot",
|
|
476
|
+
);
|
|
477
|
+
|
|
478
|
+
// non-allowlisted payee limbs (membership proof no longer folds to the root).
|
|
479
|
+
const rogue = { ...valid, rLo: "1", rHi: "2" };
|
|
480
|
+
await assert.rejects(
|
|
481
|
+
() => calc.calculateWTNSBin(rogue, true),
|
|
482
|
+
/Assert Failed|Error/,
|
|
483
|
+
"circuit must reject a non-allowlisted recipient",
|
|
484
|
+
);
|
|
485
|
+
});
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
// Proving-asset resolver: locates the Groth16 wasm + zkey for a circuit by
|
|
2
|
+
// 1. source checkout (cortex-v3/circuits/build/…), then
|
|
3
|
+
// 2. the local download cache (~/.enigma/proving-assets/<circuit>/<file>),
|
|
4
|
+
// verified against the committed sha256 manifest, then
|
|
5
|
+
// 3. a checksum-pinned download from the manifest's releaseBase.
|
|
6
|
+
// Any mismatch, absence, or network failure FAILS CLOSED with a named
|
|
7
|
+
// remediation — callers never prove against unverified bytes.
|
|
8
|
+
//
|
|
9
|
+
// Manifest: cortex-v3/proving-assets.json (committed). The zkeys are
|
|
10
|
+
// gitignored (size); the manifest is the integrity anchor.
|
|
11
|
+
import { createHash } from 'node:crypto';
|
|
12
|
+
import { createReadStream, existsSync, mkdirSync, readFileSync, renameSync } from 'node:fs';
|
|
13
|
+
import { writeFile, rm } from 'node:fs/promises';
|
|
14
|
+
import { homedir } from 'node:os';
|
|
15
|
+
import { dirname, join } from 'node:path';
|
|
16
|
+
import { fileURLToPath } from 'node:url';
|
|
17
|
+
|
|
18
|
+
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
19
|
+
const SOURCE_BUILD = join(HERE, '..', 'circuits', 'build');
|
|
20
|
+
const MANIFEST_PATH = join(HERE, '..', 'proving-assets.json');
|
|
21
|
+
|
|
22
|
+
export class ProvingAssetError extends Error {
|
|
23
|
+
constructor(message, { code = 'PROVING_ASSET', remediation = '' } = {}) {
|
|
24
|
+
super(remediation ? `${message} — ${remediation}` : message);
|
|
25
|
+
this.name = 'ProvingAssetError';
|
|
26
|
+
this.code = code;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function loadProvingAssetManifest(manifestPath = MANIFEST_PATH) {
|
|
31
|
+
if (!existsSync(manifestPath)) {
|
|
32
|
+
throw new ProvingAssetError(`proving-assets manifest missing: ${manifestPath}`, {
|
|
33
|
+
code: 'MANIFEST_MISSING',
|
|
34
|
+
remediation: 'run from a complete source checkout or reinstall the package',
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
return JSON.parse(readFileSync(manifestPath, 'utf8'));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const sha256File = (path) => new Promise((resolve, reject) => {
|
|
41
|
+
const h = createHash('sha256');
|
|
42
|
+
createReadStream(path)
|
|
43
|
+
.on('data', (d) => h.update(d))
|
|
44
|
+
.on('end', () => resolve(h.digest('hex')))
|
|
45
|
+
.on('error', reject);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const cacheDir = (circuit) =>
|
|
49
|
+
join(homedir(), '.enigma', 'proving-assets', circuit);
|
|
50
|
+
|
|
51
|
+
/** Resolve one asset kind ('wasm' | 'zkey') for a circuit; returns an absolute path. */
|
|
52
|
+
export async function resolveProvingAsset(circuitName, kind, opts = {}) {
|
|
53
|
+
const manifest = opts.manifest ?? loadProvingAssetManifest();
|
|
54
|
+
const entry = manifest.circuits?.[circuitName]?.[kind];
|
|
55
|
+
if (!entry) {
|
|
56
|
+
throw new ProvingAssetError(`manifest has no ${kind} entry for circuit ${circuitName}`, {
|
|
57
|
+
code: 'MANIFEST_ENTRY_MISSING',
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const candidates = [];
|
|
62
|
+
// 1. source checkout
|
|
63
|
+
const sourcePath = kind === 'wasm'
|
|
64
|
+
? join(SOURCE_BUILD, `${circuitName}_js`, `${circuitName}.wasm`)
|
|
65
|
+
: join(SOURCE_BUILD, `${circuitName}.zkey`);
|
|
66
|
+
candidates.push(sourcePath);
|
|
67
|
+
// 2. download cache
|
|
68
|
+
const cachedPath = join(opts.cacheRoot ?? cacheDir(circuitName), entry.file);
|
|
69
|
+
candidates.push(cachedPath);
|
|
70
|
+
|
|
71
|
+
for (const candidate of candidates) {
|
|
72
|
+
if (!existsSync(candidate)) continue;
|
|
73
|
+
const actual = await sha256File(candidate);
|
|
74
|
+
if (actual === entry.sha256) return candidate;
|
|
75
|
+
if (candidate === cachedPath) {
|
|
76
|
+
// Cached but corrupt/stale — purge so a re-download can heal it.
|
|
77
|
+
await rm(cachedPath, { force: true });
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
throw new ProvingAssetError(
|
|
81
|
+
`${candidate} sha256 ${actual} != manifest ${entry.sha256}`, {
|
|
82
|
+
code: 'CHECKSUM_MISMATCH',
|
|
83
|
+
remediation: 'the file is corrupt or from a different ceremony; delete it or run the matching setup script',
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// 3. checksum-pinned download
|
|
88
|
+
if (opts.allowDownload === false) {
|
|
89
|
+
throw new ProvingAssetError(
|
|
90
|
+
`no verified local ${kind} for ${circuitName} (download disabled for this call)`, {
|
|
91
|
+
code: 'ASSET_UNAVAILABLE',
|
|
92
|
+
remediation: 'proving assets resolve on first use, or run the matching setup script',
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
if (!manifest.releaseBase) {
|
|
96
|
+
throw new ProvingAssetError(
|
|
97
|
+
`no local ${kind} for ${circuitName} and no releaseBase configured`, {
|
|
98
|
+
code: 'ASSET_UNAVAILABLE',
|
|
99
|
+
remediation: `run cortex-v3/zk/setup*.mjs in a source checkout to generate proving keys`,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
const url = `${manifest.releaseBase}/${entry.file}`;
|
|
103
|
+
mkdirSync(dirname(cachedPath), { recursive: true });
|
|
104
|
+
const tmp = `${cachedPath}.download`;
|
|
105
|
+
try {
|
|
106
|
+
const res = await fetch(url, { redirect: 'follow' });
|
|
107
|
+
if (!res.ok || !res.body) {
|
|
108
|
+
throw new ProvingAssetError(`download failed: HTTP ${res.status} for ${url}`, {
|
|
109
|
+
code: 'DOWNLOAD_FAILED',
|
|
110
|
+
remediation: 'check connectivity, or run the setup script in a source checkout',
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
await writeFile(tmp, Buffer.from(await res.arrayBuffer()));
|
|
114
|
+
const actual = await sha256File(tmp);
|
|
115
|
+
if (actual !== entry.sha256) {
|
|
116
|
+
throw new ProvingAssetError(
|
|
117
|
+
`downloaded ${entry.file} sha256 ${actual} != manifest ${entry.sha256} — refusing to use`, {
|
|
118
|
+
code: 'DOWNLOAD_CHECKSUM_MISMATCH',
|
|
119
|
+
remediation: 'release asset does not match the committed manifest; do not prove with it',
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
renameSync(tmp, cachedPath);
|
|
123
|
+
return cachedPath;
|
|
124
|
+
} finally {
|
|
125
|
+
await rm(tmp, { force: true }).catch(() => {});
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** Resolve both assets for a circuit: { wasm, zkey } absolute paths. */
|
|
130
|
+
export async function resolveProvingAssets(circuitName, opts = {}) {
|
|
131
|
+
const manifest = opts.manifest ?? loadProvingAssetManifest();
|
|
132
|
+
const [wasm, zkey] = await Promise.all([
|
|
133
|
+
resolveProvingAsset(circuitName, 'wasm', { ...opts, manifest }),
|
|
134
|
+
resolveProvingAsset(circuitName, 'zkey', { ...opts, manifest }),
|
|
135
|
+
]);
|
|
136
|
+
return { wasm, zkey };
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** Source-checkout path for the exported on-chain VK bytes JSON. */
|
|
140
|
+
const vkSourcePath = (file) => join(SOURCE_BUILD, file);
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Resolve the on-chain verifying-key bytes JSON for a circuit: source checkout
|
|
144
|
+
* (packaged installs ship it too) → verified cache → checksum-pinned download.
|
|
145
|
+
* Returns { path, vk } with vk parsed: {alphaG1,betaG2,gammaG2,deltaG2,ic[]}.
|
|
146
|
+
*/
|
|
147
|
+
export async function resolveVerifyingKey(circuitName, opts = {}) {
|
|
148
|
+
const manifest = opts.manifest ?? loadProvingAssetManifest();
|
|
149
|
+
const entry = manifest.circuits?.[circuitName]?.vkBytes;
|
|
150
|
+
if (!entry) {
|
|
151
|
+
throw new ProvingAssetError(`manifest has no vkBytes entry for circuit ${circuitName}`, {
|
|
152
|
+
code: 'MANIFEST_ENTRY_MISSING',
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
const cachedPath = join(opts.cacheRoot ?? cacheDir(circuitName), entry.file);
|
|
156
|
+
const candidates = [vkSourcePath(entry.file), cachedPath];
|
|
157
|
+
for (const candidate of candidates) {
|
|
158
|
+
if (!existsSync(candidate)) continue;
|
|
159
|
+
const actual = await sha256File(candidate);
|
|
160
|
+
if (actual === entry.sha256) {
|
|
161
|
+
const vk = JSON.parse(readFileSync(candidate, 'utf8'));
|
|
162
|
+
return { path: candidate, vk };
|
|
163
|
+
}
|
|
164
|
+
if (candidate === cachedPath) {
|
|
165
|
+
await rm(cachedPath, { force: true });
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
throw new ProvingAssetError(
|
|
169
|
+
`${candidate} sha256 ${actual} != manifest ${entry.sha256}`, {
|
|
170
|
+
code: 'CHECKSUM_MISMATCH',
|
|
171
|
+
remediation: 'VK export does not match the committed manifest; rerun the matching setup script',
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
if (!manifest.releaseBase) {
|
|
175
|
+
throw new ProvingAssetError(`no local VK for ${circuitName} and no releaseBase configured`, {
|
|
176
|
+
code: 'ASSET_UNAVAILABLE',
|
|
177
|
+
remediation: 'run the matching cortex-v3/zk/setup*.mjs in a source checkout',
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
const url = `${manifest.releaseBase}/${entry.file}`;
|
|
181
|
+
mkdirSync(dirname(cachedPath), { recursive: true });
|
|
182
|
+
const tmp = `${cachedPath}.download`;
|
|
183
|
+
try {
|
|
184
|
+
const res = await fetch(url, { redirect: 'follow' });
|
|
185
|
+
if (!res.ok || !res.body) {
|
|
186
|
+
throw new ProvingAssetError(`download failed: HTTP ${res.status} for ${url}`, {
|
|
187
|
+
code: 'DOWNLOAD_FAILED',
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
await writeFile(tmp, Buffer.from(await res.arrayBuffer()));
|
|
191
|
+
const actual = await sha256File(tmp);
|
|
192
|
+
if (actual !== entry.sha256) {
|
|
193
|
+
throw new ProvingAssetError(
|
|
194
|
+
`downloaded ${entry.file} sha256 ${actual} != manifest ${entry.sha256} — refusing to use`, {
|
|
195
|
+
code: 'DOWNLOAD_CHECKSUM_MISMATCH',
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
renameSync(tmp, cachedPath);
|
|
199
|
+
return { path: cachedPath, vk: JSON.parse(readFileSync(cachedPath, 'utf8')) };
|
|
200
|
+
} finally {
|
|
201
|
+
await rm(tmp, { force: true }).catch(() => {});
|
|
202
|
+
}
|
|
203
|
+
}
|