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,784 @@
|
|
|
1
|
+
// sale-witness.test.mjs — tests for zk/sale-witness.mjs, the client-side
|
|
2
|
+
// witness builder for circuits/sealed_sale_release.circom (the ZKCP
|
|
3
|
+
// "Sealed Sale" release proof, private-sale path).
|
|
4
|
+
//
|
|
5
|
+
// Legs:
|
|
6
|
+
// 1. Byte/chunk round-trip — LE 31-byte limbs, every chunk < 2^248 < r;
|
|
7
|
+
// arbitrary bytes (incl. trailing 0x00, 0xFF-filled, 0/1/31/32/992
|
|
8
|
+
// bytes) round-trip exactly; >992 bytes rejected.
|
|
9
|
+
// 2. Poseidon-CTR encrypt/decrypt round-trip + tamper diffusion.
|
|
10
|
+
// 3. Poseidon-CTR KAT — pinned keystream/ciphertext/fold values, plus a
|
|
11
|
+
// fully independent recomputation (raw circomlibjs Poseidon + explicit
|
|
12
|
+
// mod-r field add; no module code).
|
|
13
|
+
// 4. BabyJubJub buyer keys — generated keys validate; encode/decode
|
|
14
|
+
// round-trip; small-order (identity, order-2, order-8) and off-curve
|
|
15
|
+
// and non-canonical points REJECTED with the circuit's exact rule
|
|
16
|
+
// (on-curve ∧ (8·B).x ≠ 0).
|
|
17
|
+
// 5. THIRD-PARTY CANNOT DECRYPT — buyer B's secret on buyer A's envelope
|
|
18
|
+
// yields the wrong DEK; digest assertion fails.
|
|
19
|
+
// 6. saleTermsHash — KAT + independent recomputation; limb/range guards.
|
|
20
|
+
// 7. buildSaleWitness — full witness shape, exact signal names (per the
|
|
21
|
+
// SaleCircuit contract), decimal strings, public-signal order; envelope
|
|
22
|
+
// artifacts self-consistent; client-side rejections throw.
|
|
23
|
+
// 8. Signal-name reconciliation vs circuits/sealed_sale_release.circom
|
|
24
|
+
// (when the file exists): every `signal input` name in the main
|
|
25
|
+
// template must match exactly, public order must match.
|
|
26
|
+
// 9. WASM-gated (polls up to 20 min for SaleCircuit build artifacts;
|
|
27
|
+
// override with SALE_WITNESS_POLL_MS=0 to skip immediately):
|
|
28
|
+
// valid witness satisfies the compiled circuit; a junk-ciphertext
|
|
29
|
+
// witness is REJECTED by the witness calculator.
|
|
30
|
+
//
|
|
31
|
+
// Run: node --test cortex-v3/zk/sale-witness.test.mjs
|
|
32
|
+
|
|
33
|
+
import test from "node:test";
|
|
34
|
+
import assert from "node:assert/strict";
|
|
35
|
+
import { execFileSync } from "node:child_process";
|
|
36
|
+
import { randomBytes } from "node:crypto";
|
|
37
|
+
import { existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
38
|
+
import { createRequire } from "node:module";
|
|
39
|
+
import { tmpdir } from "node:os";
|
|
40
|
+
import { dirname, join, resolve } from "node:path";
|
|
41
|
+
import { fileURLToPath } from "node:url";
|
|
42
|
+
|
|
43
|
+
import { buildBabyjub, buildPoseidon } from "circomlibjs";
|
|
44
|
+
import bs58 from "bs58";
|
|
45
|
+
|
|
46
|
+
import { FIELD_R, NoteTree } from "./zk-tree.mjs";
|
|
47
|
+
import {
|
|
48
|
+
CHUNK_BYTES,
|
|
49
|
+
CHUNK_COUNT,
|
|
50
|
+
DOMAIN_SALE,
|
|
51
|
+
NOTE_DEPTH,
|
|
52
|
+
PAYLOAD_CAPACITY,
|
|
53
|
+
SALE_PUBLIC_SIGNALS,
|
|
54
|
+
buildSaleDemoFixture,
|
|
55
|
+
buildSaleWitness,
|
|
56
|
+
bytesToChunks,
|
|
57
|
+
bytesToBigIntLE,
|
|
58
|
+
chunksFromBytes,
|
|
59
|
+
chunksToBytes,
|
|
60
|
+
computeKeyCommit,
|
|
61
|
+
computeSaleNote,
|
|
62
|
+
DOMAIN_PSALT,
|
|
63
|
+
computeSaleTermsHash,
|
|
64
|
+
decryptPayload,
|
|
65
|
+
deriveSharedSecret,
|
|
66
|
+
encodePoint,
|
|
67
|
+
decodePoint,
|
|
68
|
+
encryptPayload,
|
|
69
|
+
foldChunks,
|
|
70
|
+
foldChunksBlind,
|
|
71
|
+
generateBuyerKeyPair,
|
|
72
|
+
keystreamChunk,
|
|
73
|
+
openAndVerifySale,
|
|
74
|
+
openSaleEnvelope,
|
|
75
|
+
validateBuyerPublicKey,
|
|
76
|
+
} from "./sale-witness.mjs";
|
|
77
|
+
|
|
78
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
79
|
+
const PKG_ROOT = resolve(__dirname, "..");
|
|
80
|
+
const BUILD = join(PKG_ROOT, "circuits", "build");
|
|
81
|
+
const CIRCUIT_SRC = join(PKG_ROOT, "circuits", "sealed_sale_release.circom");
|
|
82
|
+
const R1CS = join(BUILD, "sealed_sale_release.r1cs");
|
|
83
|
+
const WASM = join(BUILD, "sealed_sale_release_js", "sealed_sale_release.wasm");
|
|
84
|
+
const WITNESS_CALC = join(BUILD, "sealed_sale_release_js", "witness_calculator.js");
|
|
85
|
+
|
|
86
|
+
const require = createRequire(import.meta.url);
|
|
87
|
+
|
|
88
|
+
// ---------------------------------------------------------------------------
|
|
89
|
+
// Independent machinery (raw circomlibjs — shares no code with the module)
|
|
90
|
+
// ---------------------------------------------------------------------------
|
|
91
|
+
const poseidonC = await buildPoseidon();
|
|
92
|
+
const F = poseidonC.F;
|
|
93
|
+
const R = FIELD_R;
|
|
94
|
+
const independentPoseidon = (xs) => F.toObject(poseidonC(xs.map((x) => F.e(BigInt(x)))));
|
|
95
|
+
|
|
96
|
+
const babyJub = await buildBabyjub();
|
|
97
|
+
const JF = babyJub.F;
|
|
98
|
+
|
|
99
|
+
/** Independent LE reducer (Buffer-based, no shared code with the module). */
|
|
100
|
+
function leBytes(buf) {
|
|
101
|
+
let v = 0n;
|
|
102
|
+
for (let i = buf.length - 1; i >= 0; i--) v = v * 256n + BigInt(buf[i]);
|
|
103
|
+
return v;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// ---------------------------------------------------------------------------
|
|
107
|
+
// Fixed test fixture
|
|
108
|
+
// ---------------------------------------------------------------------------
|
|
109
|
+
const PLAINTEXT = new TextEncoder().encode(
|
|
110
|
+
"Enigma sealed-sale demo memory: the cortex remembers what the chain never sees.",
|
|
111
|
+
); // 79 bytes
|
|
112
|
+
const DEK_LO = 0x0de0b6b3a7640000f1f2f3f4f5f6f7f8n;
|
|
113
|
+
const DEK_HI = 0x00112233445566778899aabbccddeeffn;
|
|
114
|
+
const NONCE = 424242424242n;
|
|
115
|
+
const ESK = 0x1f1e1d1c1b1a191817161514131211100f0e0d0c0b0a09080706050403n;
|
|
116
|
+
const OWNER_KEY = 11111111111111111111111111111111111111111111111111n;
|
|
117
|
+
const RHO = 22222222222222222222222222222222222222222222222222n;
|
|
118
|
+
const SELLER = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"; // USDC mint addr as seller
|
|
119
|
+
const MINT = "So11111111111111111111111111111111111111112"; // wSOL mint
|
|
120
|
+
const PRICE = 2_000_000_000n;
|
|
121
|
+
const EXPIRY_SLOT = 300_000_000n;
|
|
122
|
+
|
|
123
|
+
// Pinned by the independent recomputation below (raw circomlibjs Poseidon,
|
|
124
|
+
// explicit mod-r field add — computed once, then frozen here).
|
|
125
|
+
const KAT = Object.freeze({
|
|
126
|
+
domainSale: 122622912455040968075340901n,
|
|
127
|
+
ks0: 3589178275523291951363275889201219803941083386281678158752858657278878496187n,
|
|
128
|
+
ks1: 14540676613757474377696583013418111239504349028389538056672046070098961572735n,
|
|
129
|
+
ks31: 19323069819120820054275317198827642037090427957986178685749720135057254853371n,
|
|
130
|
+
p0: 103315326233695364645767690488868881140320643153680381253010726472875863621n,
|
|
131
|
+
c0: 3692493601756987316009043579690088685081404029435358540005869383751754359808n,
|
|
132
|
+
payloadCommitPos: 8764819824630054939208652781887457096300910414137815727281144740307113585380n, // DEK-blinded fold
|
|
133
|
+
keyCommit: 12602673196205851515633971187412354398057859532683823173342270131645853115010n,
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
/** Deterministic buyer key pair (fixed secret, < subOrder). */
|
|
137
|
+
function demoBuyer() {
|
|
138
|
+
const secret = 0x0a0b0c0d0e0f101112131415161718191a1b1c1d1e1f2021222324252n %
|
|
139
|
+
BigInt(babyJub.subOrder.toString());
|
|
140
|
+
const P = babyJub.mulPointEscalar(babyJub.Base8, secret);
|
|
141
|
+
return { secret, publicKey: { x: JF.toObject(P[0]), y: JF.toObject(P[1]) } };
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/** Build the standard valid fixture: note tree + witness args. */
|
|
145
|
+
function makeFixture() {
|
|
146
|
+
const buyer = demoBuyer();
|
|
147
|
+
const plaintextChunks = bytesToChunks(PLAINTEXT);
|
|
148
|
+
const payloadCommitPos = foldChunksBlind(plaintextChunks, DEK_LO, DEK_HI);
|
|
149
|
+
const noteTree = new NoteTree();
|
|
150
|
+
noteTree.insert(555n); // filler at index 0
|
|
151
|
+
noteTree.insert(computeSaleNote(OWNER_KEY, RHO, payloadCommitPos)); // index 1
|
|
152
|
+
return {
|
|
153
|
+
buyer,
|
|
154
|
+
noteTree,
|
|
155
|
+
args: {
|
|
156
|
+
noteTree,
|
|
157
|
+
noteIndex: 1,
|
|
158
|
+
ownerKey: OWNER_KEY,
|
|
159
|
+
rho: RHO,
|
|
160
|
+
plaintextBytes: PLAINTEXT,
|
|
161
|
+
dekLo: DEK_LO,
|
|
162
|
+
dekHi: DEK_HI,
|
|
163
|
+
nonce: NONCE,
|
|
164
|
+
buyerPub: buyer.publicKey,
|
|
165
|
+
sellerPubkey: SELLER,
|
|
166
|
+
mint: MINT,
|
|
167
|
+
priceLamports: PRICE,
|
|
168
|
+
expirySlot: EXPIRY_SLOT,
|
|
169
|
+
esk: ESK,
|
|
170
|
+
},
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const EXPECTED_SIGNALS = Object.freeze([
|
|
175
|
+
...SALE_PUBLIC_SIGNALS,
|
|
176
|
+
"ownerKey",
|
|
177
|
+
"rho",
|
|
178
|
+
"notePathElements",
|
|
179
|
+
"notePathIndices",
|
|
180
|
+
"plaintext",
|
|
181
|
+
"ciphertext",
|
|
182
|
+
"nonce",
|
|
183
|
+
"dekLo",
|
|
184
|
+
"dekHi",
|
|
185
|
+
"buyerPubX",
|
|
186
|
+
"buyerPubY",
|
|
187
|
+
"esk",
|
|
188
|
+
"sellerLo",
|
|
189
|
+
"sellerHi",
|
|
190
|
+
"mintLo",
|
|
191
|
+
"mintHi",
|
|
192
|
+
"price",
|
|
193
|
+
"expirySlot",
|
|
194
|
+
"payloadLen",
|
|
195
|
+
]);
|
|
196
|
+
|
|
197
|
+
// ---------------------------------------------------------------------------
|
|
198
|
+
// 1. Byte/chunk round-trip
|
|
199
|
+
// ---------------------------------------------------------------------------
|
|
200
|
+
test("bytesToChunks/chunksToBytes round-trip arbitrary bytes", () => {
|
|
201
|
+
const cases = [
|
|
202
|
+
new Uint8Array(0),
|
|
203
|
+
new Uint8Array([0x42]),
|
|
204
|
+
new Uint8Array([0, 0, 0]), // all zeros
|
|
205
|
+
new Uint8Array(randomBytes(1)),
|
|
206
|
+
new Uint8Array(randomBytes(CHUNK_BYTES)), // exactly one chunk
|
|
207
|
+
new Uint8Array(randomBytes(CHUNK_BYTES + 1)), // crosses a chunk boundary
|
|
208
|
+
new Uint8Array(randomBytes(PAYLOAD_CAPACITY)), // full capacity
|
|
209
|
+
(() => {
|
|
210
|
+
const b = new Uint8Array(randomBytes(100));
|
|
211
|
+
b.fill(0, 90); // trailing zero bytes MUST round-trip
|
|
212
|
+
return b;
|
|
213
|
+
})(),
|
|
214
|
+
new Uint8Array(PAYLOAD_CAPACITY).fill(0xff), // max-entropy limbs
|
|
215
|
+
];
|
|
216
|
+
for (const bytes of cases) {
|
|
217
|
+
const chunks = bytesToChunks(bytes);
|
|
218
|
+
assert.equal(chunks.length, CHUNK_COUNT);
|
|
219
|
+
for (const c of chunks) {
|
|
220
|
+
assert.ok(c >= 0n && c < 1n << 248n, `chunk ${c} outside [0, 2^248)`);
|
|
221
|
+
assert.ok(c < R, `chunk ${c} not canonical mod r`);
|
|
222
|
+
}
|
|
223
|
+
const back = chunksToBytes(chunks, bytes.length);
|
|
224
|
+
assert.deepEqual(back, bytes, `round-trip failed for ${bytes.length} bytes`);
|
|
225
|
+
// aliases must agree
|
|
226
|
+
assert.deepEqual(chunksFromBytes(bytes), chunks);
|
|
227
|
+
}
|
|
228
|
+
// default length = full capacity
|
|
229
|
+
const full = chunksToBytes(bytesToChunks(new Uint8Array([7])));
|
|
230
|
+
assert.equal(full.length, PAYLOAD_CAPACITY);
|
|
231
|
+
assert.equal(full[0], 7);
|
|
232
|
+
assert.ok(full.subarray(1).every((b) => b === 0));
|
|
233
|
+
// over-capacity payload rejected
|
|
234
|
+
assert.throws(() => bytesToChunks(new Uint8Array(PAYLOAD_CAPACITY + 1)), /too large/);
|
|
235
|
+
// out-of-range chunk rejected on decode
|
|
236
|
+
const bad = new Array(CHUNK_COUNT).fill(0n);
|
|
237
|
+
bad[3] = 1n << 248n;
|
|
238
|
+
assert.throws(() => chunksToBytes(bad), /outside \[0, 2\^248\)/);
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
// ---------------------------------------------------------------------------
|
|
242
|
+
// 2. Poseidon-CTR round-trip
|
|
243
|
+
// ---------------------------------------------------------------------------
|
|
244
|
+
test("encryptPayload/decryptPayload round-trip; wrong key/nonce garbles", () => {
|
|
245
|
+
const chunks = bytesToChunks(new Uint8Array(randomBytes(500)));
|
|
246
|
+
const dekLo = bytesToBigIntLE(randomBytes(16));
|
|
247
|
+
const dekHi = bytesToBigIntLE(randomBytes(16));
|
|
248
|
+
const nonce = bytesToBigIntLE(randomBytes(16));
|
|
249
|
+
|
|
250
|
+
const ct = encryptPayload(chunks, dekLo, dekHi, nonce);
|
|
251
|
+
assert.equal(ct.length, CHUNK_COUNT);
|
|
252
|
+
for (const c of ct) assert.ok(c >= 0n && c < R, "ciphertext chunk not canonical");
|
|
253
|
+
assert.notDeepEqual(ct, chunks, "ciphertext must differ from plaintext");
|
|
254
|
+
assert.deepEqual(decryptPayload(ct, dekLo, dekHi, nonce), chunks);
|
|
255
|
+
|
|
256
|
+
// field-add cipher: c_i - p_i === ks_i (mod r) — the circuit constraint
|
|
257
|
+
const ks0 = keystreamChunk(dekLo, dekHi, nonce, 0);
|
|
258
|
+
assert.equal((ct[0] - chunks[0] + R) % R, ks0);
|
|
259
|
+
|
|
260
|
+
// wrong DEK → wrong plaintext → different fold
|
|
261
|
+
const wrong = decryptPayload(ct, dekLo ^ 1n, dekHi, nonce);
|
|
262
|
+
assert.notDeepEqual(wrong, chunks);
|
|
263
|
+
assert.notEqual(foldChunks(wrong), foldChunks(chunks));
|
|
264
|
+
// wrong nonce → same
|
|
265
|
+
const wrongN = decryptPayload(ct, dekLo, dekHi, nonce + 1n);
|
|
266
|
+
assert.notDeepEqual(wrongN, chunks);
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
// ---------------------------------------------------------------------------
|
|
270
|
+
// 3. Poseidon-CTR KAT (pinned + independent recomputation)
|
|
271
|
+
// ---------------------------------------------------------------------------
|
|
272
|
+
test("Poseidon-CTR matches pinned KAT and independent recomputation", () => {
|
|
273
|
+
assert.equal(DOMAIN_SALE, KAT.domainSale, "DOMAIN_SALE drifted");
|
|
274
|
+
|
|
275
|
+
// pinned keystream values
|
|
276
|
+
assert.equal(keystreamChunk(DEK_LO, DEK_HI, NONCE, 0), KAT.ks0);
|
|
277
|
+
assert.equal(keystreamChunk(DEK_LO, DEK_HI, NONCE, 1), KAT.ks1);
|
|
278
|
+
assert.equal(keystreamChunk(DEK_LO, DEK_HI, NONCE, 31), KAT.ks31);
|
|
279
|
+
|
|
280
|
+
// independent keystream recomputation (raw circomlibjs)
|
|
281
|
+
for (const [i, pinned] of [[0n, KAT.ks0], [1n, KAT.ks1], [31n, KAT.ks31]]) {
|
|
282
|
+
assert.equal(
|
|
283
|
+
independentPoseidon([DEK_LO, DEK_HI, NONCE, i]),
|
|
284
|
+
pinned,
|
|
285
|
+
`independent Poseidon4 recomputation disagrees at i=${i}`,
|
|
286
|
+
);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// independent chunk derivation + encryption of the fixture plaintext
|
|
290
|
+
const p0 = leBytes(Buffer.from(PLAINTEXT.subarray(0, 31)));
|
|
291
|
+
assert.equal(p0, KAT.p0);
|
|
292
|
+
const c0 = (p0 + independentPoseidon([DEK_LO, DEK_HI, NONCE, 0n])) % R;
|
|
293
|
+
assert.equal(c0, KAT.c0, "independent field-add encryption disagrees");
|
|
294
|
+
|
|
295
|
+
const chunks = bytesToChunks(PLAINTEXT);
|
|
296
|
+
assert.equal(chunks[0], KAT.p0);
|
|
297
|
+
assert.equal(encryptPayload(chunks, DEK_LO, DEK_HI, NONCE)[0], KAT.c0);
|
|
298
|
+
|
|
299
|
+
// fold KAT (payloadCommitPos of the fixture plaintext — DEK-blinded fold)
|
|
300
|
+
let acc = independentPoseidon([DOMAIN_PSALT, DEK_LO, DEK_HI]);
|
|
301
|
+
for (const c of chunks) acc = independentPoseidon([acc, c]);
|
|
302
|
+
assert.equal(acc, KAT.payloadCommitPos);
|
|
303
|
+
assert.equal(foldChunksBlind(chunks, DEK_LO, DEK_HI), KAT.payloadCommitPos);
|
|
304
|
+
// a different DEK seeds a different commitment (dictionary-defense property)
|
|
305
|
+
assert.notEqual(foldChunksBlind(chunks, DEK_LO + 1n, DEK_HI), KAT.payloadCommitPos);
|
|
306
|
+
|
|
307
|
+
// keyCommit KAT
|
|
308
|
+
assert.equal(computeKeyCommit(DEK_LO, DEK_HI), KAT.keyCommit);
|
|
309
|
+
assert.equal(independentPoseidon([DEK_LO, DEK_HI]), KAT.keyCommit);
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
// ---------------------------------------------------------------------------
|
|
313
|
+
// 4. BabyJubJub buyer keys
|
|
314
|
+
// ---------------------------------------------------------------------------
|
|
315
|
+
test("buyer key generation, encode/decode, and validation (circuit-exact rule)", () => {
|
|
316
|
+
const kp = generateBuyerKeyPair();
|
|
317
|
+
assert.ok(kp.secret > 0n && kp.secret < BigInt(babyJub.subOrder.toString()));
|
|
318
|
+
// on-curve per raw circomlibjs
|
|
319
|
+
assert.ok(babyJub.inCurve([JF.e(kp.publicKey.x), JF.e(kp.publicKey.y)]));
|
|
320
|
+
// validateBuyerPublicKey accepts and returns canonical bigints
|
|
321
|
+
assert.deepEqual(validateBuyerPublicKey(kp.publicKey), [kp.publicKey.x, kp.publicKey.y]);
|
|
322
|
+
|
|
323
|
+
// point encode/decode round-trip (decimal strings)
|
|
324
|
+
const enc = encodePoint(kp.publicKey);
|
|
325
|
+
assert.match(enc.x, /^\d+$/);
|
|
326
|
+
assert.match(enc.y, /^\d+$/);
|
|
327
|
+
assert.deepEqual(decodePoint(enc), [kp.publicKey.x, kp.publicKey.y]);
|
|
328
|
+
assert.deepEqual(validateBuyerPublicKey(enc), [kp.publicKey.x, kp.publicKey.y]);
|
|
329
|
+
|
|
330
|
+
// Base8 itself is a valid recipient key
|
|
331
|
+
assert.deepEqual(validateBuyerPublicKey(encodePoint(babyJub.Base8)), [
|
|
332
|
+
JF.toObject(babyJub.Base8[0]),
|
|
333
|
+
JF.toObject(babyJub.Base8[1]),
|
|
334
|
+
]);
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
test("small-order, off-curve, and non-canonical buyer keys are rejected", () => {
|
|
338
|
+
// identity (0, 1) — order 1
|
|
339
|
+
assert.throws(() => validateBuyerPublicKey({ x: 0n, y: 1n }), /small-order/);
|
|
340
|
+
// order-2 point (0, -1) = (0, r-1)
|
|
341
|
+
assert.throws(() => validateBuyerPublicKey({ x: 0n, y: R - 1n }), /small-order/);
|
|
342
|
+
// order-8 point: subOrder · Generator (Generator spans the full group, so
|
|
343
|
+
// multiplying by the prime cofactor quotient lands in the 8-torsion; it has
|
|
344
|
+
// x ≠ 0, so this exercises the 8·B x-check and not the on-curve equation).
|
|
345
|
+
const torsion = babyJub.mulPointEscalar(babyJub.Generator, babyJub.subOrder);
|
|
346
|
+
const t8 = babyJub.mulPointEscalar(torsion, 8n);
|
|
347
|
+
assert.ok(JF.isZero(t8[0]), "test premise: chosen point must be 8-torsion");
|
|
348
|
+
assert.ok(babyJub.inCurve(torsion));
|
|
349
|
+
assert.throws(
|
|
350
|
+
() => validateBuyerPublicKey({ x: JF.toObject(torsion[0]), y: JF.toObject(torsion[1]) }),
|
|
351
|
+
/small-order/,
|
|
352
|
+
);
|
|
353
|
+
// off-curve point
|
|
354
|
+
assert.throws(() => validateBuyerPublicKey({ x: 1n, y: 1n }), /not on the BabyJubJub curve/);
|
|
355
|
+
// non-canonical coordinates (>= r) — must never reach the curve check
|
|
356
|
+
assert.throws(() => validateBuyerPublicKey({ x: R, y: 1n }), /canonical/);
|
|
357
|
+
assert.throws(() => validateBuyerPublicKey({ x: 1n, y: R + 5n }), /canonical/);
|
|
358
|
+
// malformed shape
|
|
359
|
+
assert.throws(() => validateBuyerPublicKey({}), /expected \{x, y\}/);
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
// ---------------------------------------------------------------------------
|
|
363
|
+
// 5. Third-party cannot decrypt
|
|
364
|
+
// ---------------------------------------------------------------------------
|
|
365
|
+
test("third party cannot decrypt: wrong buyer secret fails the digest assert", () => {
|
|
366
|
+
const { args, buyer } = makeFixture();
|
|
367
|
+
const result = buildSaleWitness(args);
|
|
368
|
+
|
|
369
|
+
// intended buyer opens the envelope and the digest checks out
|
|
370
|
+
const opened = openAndVerifySale({
|
|
371
|
+
buyerSecret: buyer.secret,
|
|
372
|
+
epk: result.epk,
|
|
373
|
+
encDEK_lo: result.encDEK_lo,
|
|
374
|
+
encDEK_hi: result.encDEK_hi,
|
|
375
|
+
ciphertext: result.ciphertextChunks,
|
|
376
|
+
cipherCommit: result.cipherCommit,
|
|
377
|
+
nonce: NONCE,
|
|
378
|
+
payloadCommitPos: result.payloadCommitPos,
|
|
379
|
+
});
|
|
380
|
+
assert.equal(opened.cipherCommit, result.cipherCommit);
|
|
381
|
+
assert.equal(opened.dekLo, DEK_LO);
|
|
382
|
+
assert.equal(opened.dekHi, DEK_HI);
|
|
383
|
+
assert.deepEqual(opened.plaintextChunks, result.plaintextChunks);
|
|
384
|
+
|
|
385
|
+
// a third party (buyer B) derives a different shared secret → wrong DEK
|
|
386
|
+
const mallory = generateBuyerKeyPair();
|
|
387
|
+
const stolen = openSaleEnvelope(
|
|
388
|
+
mallory.secret,
|
|
389
|
+
result.epk,
|
|
390
|
+
result.encDEK_lo,
|
|
391
|
+
result.encDEK_hi,
|
|
392
|
+
);
|
|
393
|
+
assert.notEqual(stolen.dekLo, DEK_LO, "third party must NOT recover the DEK");
|
|
394
|
+
assert.notEqual(stolen.dekHi, DEK_HI);
|
|
395
|
+
|
|
396
|
+
// ...and the digest assertion rejects the garbage plaintext
|
|
397
|
+
assert.throws(
|
|
398
|
+
() =>
|
|
399
|
+
openAndVerifySale({
|
|
400
|
+
buyerSecret: mallory.secret,
|
|
401
|
+
epk: result.epk,
|
|
402
|
+
encDEK_lo: result.encDEK_lo,
|
|
403
|
+
encDEK_hi: result.encDEK_hi,
|
|
404
|
+
ciphertext: result.ciphertextChunks,
|
|
405
|
+
cipherCommit: result.cipherCommit,
|
|
406
|
+
nonce: NONCE,
|
|
407
|
+
payloadCommitPos: result.payloadCommitPos,
|
|
408
|
+
}),
|
|
409
|
+
/digest mismatch/,
|
|
410
|
+
);
|
|
411
|
+
|
|
412
|
+
// junk delivery: ciphertext that does NOT fold to the public cipherCommit
|
|
413
|
+
// is rejected before any decryption — this is the pre-payment defense.
|
|
414
|
+
const junk = [...result.ciphertextChunks];
|
|
415
|
+
junk[0] = (junk[0] + 1n) % R;
|
|
416
|
+
assert.throws(
|
|
417
|
+
() =>
|
|
418
|
+
openAndVerifySale({
|
|
419
|
+
buyerSecret: buyer.secret,
|
|
420
|
+
epk: result.epk,
|
|
421
|
+
encDEK_lo: result.encDEK_lo,
|
|
422
|
+
encDEK_hi: result.encDEK_hi,
|
|
423
|
+
ciphertext: junk,
|
|
424
|
+
cipherCommit: result.cipherCommit,
|
|
425
|
+
nonce: NONCE,
|
|
426
|
+
payloadCommitPos: result.payloadCommitPos,
|
|
427
|
+
}),
|
|
428
|
+
/junk delivery/,
|
|
429
|
+
);
|
|
430
|
+
// cipherCommit is mandatory (it is the public, on-chain commitment)
|
|
431
|
+
assert.throws(
|
|
432
|
+
() =>
|
|
433
|
+
openAndVerifySale({
|
|
434
|
+
buyerSecret: buyer.secret,
|
|
435
|
+
epk: result.epk,
|
|
436
|
+
encDEK_lo: result.encDEK_lo,
|
|
437
|
+
encDEK_hi: result.encDEK_hi,
|
|
438
|
+
ciphertext: result.ciphertextChunks,
|
|
439
|
+
nonce: NONCE,
|
|
440
|
+
payloadCommitPos: result.payloadCommitPos,
|
|
441
|
+
}),
|
|
442
|
+
/missing cipherCommit/,
|
|
443
|
+
);
|
|
444
|
+
|
|
445
|
+
// sanity: both parties agree on ss only when the secret matches
|
|
446
|
+
const sellerSide = deriveSharedSecret(ESK, buyer.publicKey);
|
|
447
|
+
const buyerSide = deriveSharedSecret(buyer.secret, result.epk);
|
|
448
|
+
assert.equal(sellerSide.ss, buyerSide.ss, "ECDH must agree for the intended pair");
|
|
449
|
+
const mallorySide = deriveSharedSecret(mallory.secret, result.epk);
|
|
450
|
+
assert.notEqual(mallorySide.ss, buyerSide.ss);
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
// ---------------------------------------------------------------------------
|
|
454
|
+
// 6. saleTermsHash
|
|
455
|
+
// ---------------------------------------------------------------------------
|
|
456
|
+
test("computeSaleTermsHash matches independent recomputation; guards ranges", () => {
|
|
457
|
+
const buyer = demoBuyer();
|
|
458
|
+
const out = computeSaleTermsHash({
|
|
459
|
+
sellerPubkey: SELLER,
|
|
460
|
+
buyerPub: buyer.publicKey,
|
|
461
|
+
mint: MINT,
|
|
462
|
+
payloadLen: 79n,
|
|
463
|
+
priceLamports: PRICE,
|
|
464
|
+
expirySlot: EXPIRY_SLOT,
|
|
465
|
+
});
|
|
466
|
+
const s = bs58.decode(SELLER);
|
|
467
|
+
const m = bs58.decode(MINT);
|
|
468
|
+
assert.equal(out.sellerLo, leBytes(s.subarray(0, 16)));
|
|
469
|
+
assert.equal(out.sellerHi, leBytes(s.subarray(16, 32)));
|
|
470
|
+
assert.equal(out.mintLo, leBytes(m.subarray(0, 16)));
|
|
471
|
+
assert.equal(out.mintHi, leBytes(m.subarray(16, 32)));
|
|
472
|
+
for (const limb of [out.sellerLo, out.sellerHi, out.mintLo, out.mintHi]) {
|
|
473
|
+
assert.ok(limb < 1n << 128n);
|
|
474
|
+
}
|
|
475
|
+
assert.equal(out.buyerPubX, buyer.publicKey.x);
|
|
476
|
+
assert.equal(out.buyerPubY, buyer.publicKey.y);
|
|
477
|
+
// Poseidon10: buyer pubkey limbs sit between sellerHi and mintLo; payloadLen last
|
|
478
|
+
assert.equal(
|
|
479
|
+
out.saleTermsHash,
|
|
480
|
+
independentPoseidon([
|
|
481
|
+
DOMAIN_SALE,
|
|
482
|
+
out.sellerLo,
|
|
483
|
+
out.sellerHi,
|
|
484
|
+
buyer.publicKey.x,
|
|
485
|
+
buyer.publicKey.y,
|
|
486
|
+
out.mintLo,
|
|
487
|
+
out.mintHi,
|
|
488
|
+
PRICE,
|
|
489
|
+
EXPIRY_SLOT,
|
|
490
|
+
79n,
|
|
491
|
+
]),
|
|
492
|
+
);
|
|
493
|
+
|
|
494
|
+
// collusion defense: a different buyer key MUST change the terms hash, so a
|
|
495
|
+
// receipt wrapped to a colluder can never match an honest buyer's escrow.
|
|
496
|
+
const colluder = generateBuyerKeyPair();
|
|
497
|
+
const colluderTerms = computeSaleTermsHash({
|
|
498
|
+
sellerPubkey: SELLER,
|
|
499
|
+
buyerPub: colluder.publicKey,
|
|
500
|
+
mint: MINT,
|
|
501
|
+
payloadLen: 79n,
|
|
502
|
+
priceLamports: PRICE,
|
|
503
|
+
expirySlot: EXPIRY_SLOT,
|
|
504
|
+
});
|
|
505
|
+
assert.notEqual(colluderTerms.saleTermsHash, out.saleTermsHash);
|
|
506
|
+
|
|
507
|
+
assert.throws(
|
|
508
|
+
() => computeSaleTermsHash({ sellerPubkey: "1111", buyerPub: buyer.publicKey, mint: MINT, priceLamports: PRICE, expirySlot: EXPIRY_SLOT, payloadLen: 79n }),
|
|
509
|
+
/32 bytes/,
|
|
510
|
+
);
|
|
511
|
+
assert.throws(
|
|
512
|
+
() => computeSaleTermsHash({ sellerPubkey: SELLER, buyerPub: { x: 0n, y: 1n }, mint: MINT, priceLamports: PRICE, expirySlot: EXPIRY_SLOT, payloadLen: 79n }),
|
|
513
|
+
/small-order/,
|
|
514
|
+
);
|
|
515
|
+
assert.throws(
|
|
516
|
+
() => computeSaleTermsHash({ sellerPubkey: SELLER, buyerPub: buyer.publicKey, mint: MINT, priceLamports: 1n << 64n, expirySlot: EXPIRY_SLOT, payloadLen: 79n }),
|
|
517
|
+
/u64/,
|
|
518
|
+
);
|
|
519
|
+
assert.throws(
|
|
520
|
+
() => computeSaleTermsHash({ sellerPubkey: SELLER, buyerPub: buyer.publicKey, mint: MINT, priceLamports: PRICE, expirySlot: 1n << 48n, payloadLen: 79n }),
|
|
521
|
+
/48-bit/,
|
|
522
|
+
);
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
// ---------------------------------------------------------------------------
|
|
526
|
+
// 7. buildSaleWitness
|
|
527
|
+
// ---------------------------------------------------------------------------
|
|
528
|
+
test("buildSaleWitness emits exact signal names, decimal strings, public order", () => {
|
|
529
|
+
const { args, noteTree, buyer } = makeFixture();
|
|
530
|
+
const r = buildSaleWitness(args);
|
|
531
|
+
const w = r.input;
|
|
532
|
+
|
|
533
|
+
assert.deepEqual(
|
|
534
|
+
Object.keys(w).sort(),
|
|
535
|
+
[...EXPECTED_SIGNALS].sort(),
|
|
536
|
+
"witness signal-name set drifted from the circuit spec",
|
|
537
|
+
);
|
|
538
|
+
for (const [k, v] of Object.entries(w)) {
|
|
539
|
+
if (Array.isArray(v)) {
|
|
540
|
+
for (const x of v) assert.match(x, /^\d+$/, `${k} element not a decimal string`);
|
|
541
|
+
} else {
|
|
542
|
+
assert.match(v, /^\d+$/, `${k} not a decimal string`);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
assert.equal(w.notePathElements.length, NOTE_DEPTH);
|
|
547
|
+
assert.equal(w.notePathIndices.length, NOTE_DEPTH);
|
|
548
|
+
assert.equal(w.plaintext.length, CHUNK_COUNT);
|
|
549
|
+
assert.equal(w.ciphertext.length, CHUNK_COUNT);
|
|
550
|
+
|
|
551
|
+
// public values (exact order contract — 6 publics, cipherCommit at index 2)
|
|
552
|
+
assert.deepEqual(
|
|
553
|
+
SALE_PUBLIC_SIGNALS,
|
|
554
|
+
["noteRoot", "payloadCommitPos", "cipherCommit", "keyCommit", "saleTermsHash", "envelopeCommit"],
|
|
555
|
+
);
|
|
556
|
+
assert.equal(w.cipherCommit, foldChunks(r.ciphertextChunks).toString());
|
|
557
|
+
assert.equal(w.noteRoot, noteTree.root().toString());
|
|
558
|
+
assert.equal(w.payloadCommitPos, KAT.payloadCommitPos.toString());
|
|
559
|
+
assert.equal(w.keyCommit, KAT.keyCommit.toString());
|
|
560
|
+
assert.equal(
|
|
561
|
+
w.saleTermsHash,
|
|
562
|
+
computeSaleTermsHash({
|
|
563
|
+
sellerPubkey: SELLER,
|
|
564
|
+
buyerPub: buyer.publicKey,
|
|
565
|
+
mint: MINT,
|
|
566
|
+
payloadLen: 79n,
|
|
567
|
+
priceLamports: PRICE,
|
|
568
|
+
expirySlot: EXPIRY_SLOT,
|
|
569
|
+
}).saleTermsHash.toString(),
|
|
570
|
+
);
|
|
571
|
+
// buyer-bound terms: the witness buyerPubX/Y feed the terms hash
|
|
572
|
+
assert.equal(w.saleTermsHash, r.saleTermsHash.toString());
|
|
573
|
+
assert.equal(r.buyerPubX, buyer.publicKey.x);
|
|
574
|
+
assert.equal(r.buyerPubY, buyer.publicKey.y);
|
|
575
|
+
|
|
576
|
+
// envelope self-consistency (independent recomputation from raw signals)
|
|
577
|
+
const epkP = babyJub.mulPointEscalar(babyJub.Base8, ESK);
|
|
578
|
+
assert.deepEqual(r.epk, { x: JF.toObject(epkP[0]), y: JF.toObject(epkP[1]) });
|
|
579
|
+
const sharedP = babyJub.mulPointEscalar([JF.e(buyer.publicKey.x), JF.e(buyer.publicKey.y)], ESK);
|
|
580
|
+
const ss = independentPoseidon([JF.toObject(sharedP[0]), JF.toObject(sharedP[1])]);
|
|
581
|
+
assert.equal(r.encDEK_lo, (DEK_LO + independentPoseidon([ss, 0n])) % R);
|
|
582
|
+
assert.equal(r.encDEK_hi, (DEK_HI + independentPoseidon([ss, 1n])) % R);
|
|
583
|
+
assert.equal(
|
|
584
|
+
r.envelopeCommit,
|
|
585
|
+
independentPoseidon([r.epk.x, r.epk.y, r.encDEK_lo, r.encDEK_hi, NONCE]),
|
|
586
|
+
);
|
|
587
|
+
assert.equal(w.envelopeCommit, r.envelopeCommit.toString());
|
|
588
|
+
|
|
589
|
+
// private consistency
|
|
590
|
+
assert.equal(w.dekLo, DEK_LO.toString());
|
|
591
|
+
assert.equal(w.dekHi, DEK_HI.toString());
|
|
592
|
+
assert.equal(w.nonce, NONCE.toString());
|
|
593
|
+
assert.equal(w.esk, ESK.toString());
|
|
594
|
+
assert.equal(w.buyerPubX, buyer.publicKey.x.toString());
|
|
595
|
+
assert.equal(w.buyerPubY, buyer.publicKey.y.toString());
|
|
596
|
+
assert.equal(w.cipherCommit, foldChunks(r.ciphertextChunks).toString());
|
|
597
|
+
assert.equal(BigInt(w.plaintext[0]), KAT.p0);
|
|
598
|
+
assert.equal(BigInt(w.ciphertext[0]), KAT.c0);
|
|
599
|
+
});
|
|
600
|
+
|
|
601
|
+
test("buildSaleWitness rejects malformed sales client-side", () => {
|
|
602
|
+
const { args } = makeFixture();
|
|
603
|
+
// note opening mismatch
|
|
604
|
+
assert.throws(() => buildSaleWitness({ ...args, rho: RHO + 1n }), /does not match the leaf/);
|
|
605
|
+
// stale noteIndex
|
|
606
|
+
assert.throws(() => buildSaleWitness({ ...args, noteIndex: 9 }), /not present/);
|
|
607
|
+
// DEK limb overflow
|
|
608
|
+
assert.throws(() => buildSaleWitness({ ...args, dekLo: 1n << 128n }), /2\^128/);
|
|
609
|
+
// small-order buyer key
|
|
610
|
+
assert.throws(() => buildSaleWitness({ ...args, buyerPub: { x: 0n, y: 1n } }), /small-order/);
|
|
611
|
+
// off-curve buyer key
|
|
612
|
+
assert.throws(() => buildSaleWitness({ ...args, buyerPub: { x: 1n, y: 1n } }), /not on/);
|
|
613
|
+
// esk out of Num2Bits(253) range
|
|
614
|
+
assert.throws(() => buildSaleWitness({ ...args, esk: 1n << 253n }), /253/);
|
|
615
|
+
// oversize plaintext
|
|
616
|
+
assert.throws(
|
|
617
|
+
() => buildSaleWitness({ ...args, plaintextBytes: new Uint8Array(PAYLOAD_CAPACITY + 1) }),
|
|
618
|
+
/out of range|too large/,
|
|
619
|
+
);
|
|
620
|
+
// random esk path (no esk supplied) still yields a valid witness
|
|
621
|
+
const { args: args2 } = makeFixture();
|
|
622
|
+
delete args2.esk;
|
|
623
|
+
const r2 = buildSaleWitness(args2);
|
|
624
|
+
assert.ok(BigInt(r2.input.esk) < 1n << 253n);
|
|
625
|
+
});
|
|
626
|
+
|
|
627
|
+
// ---------------------------------------------------------------------------
|
|
628
|
+
// 7b. Demo fixture contract (consumed by zk/setup-sale.mjs proof-of-life)
|
|
629
|
+
// ---------------------------------------------------------------------------
|
|
630
|
+
test("buildSaleDemoFixture returns input + expectedPublic in circuit order", async () => {
|
|
631
|
+
const fx = await buildSaleDemoFixture();
|
|
632
|
+
assert.deepEqual(Object.keys(fx.input).sort(), [...EXPECTED_SIGNALS].sort());
|
|
633
|
+
assert.equal(fx.expectedPublic.length, 6);
|
|
634
|
+
assert.deepEqual(
|
|
635
|
+
fx.expectedPublic,
|
|
636
|
+
SALE_PUBLIC_SIGNALS.map((n) => fx.input[n]),
|
|
637
|
+
);
|
|
638
|
+
for (const v of fx.expectedPublic) assert.match(v, /^\d+$/);
|
|
639
|
+
// buyer can open the demo envelope
|
|
640
|
+
const opened = openAndVerifySale({
|
|
641
|
+
buyerSecret: fx.buyerSecret,
|
|
642
|
+
epk: fx.artifacts.epk,
|
|
643
|
+
encDEK_lo: fx.artifacts.encDEK_lo,
|
|
644
|
+
encDEK_hi: fx.artifacts.encDEK_hi,
|
|
645
|
+
ciphertext: fx.artifacts.ciphertextChunks,
|
|
646
|
+
cipherCommit: fx.artifacts.cipherCommit,
|
|
647
|
+
nonce: fx.input.nonce,
|
|
648
|
+
payloadCommitPos: fx.artifacts.payloadCommitPos,
|
|
649
|
+
});
|
|
650
|
+
assert.deepEqual(
|
|
651
|
+
chunksToBytes(opened.plaintextChunks, fx.plaintextBytes.length),
|
|
652
|
+
fx.plaintextBytes,
|
|
653
|
+
);
|
|
654
|
+
});
|
|
655
|
+
|
|
656
|
+
// ---------------------------------------------------------------------------
|
|
657
|
+
// 8. Signal-name reconciliation vs the .circom source (when present)
|
|
658
|
+
// ---------------------------------------------------------------------------
|
|
659
|
+
test("signal names reconcile with circuits/sealed_sale_release.circom", (t) => {
|
|
660
|
+
if (!existsSync(CIRCUIT_SRC)) {
|
|
661
|
+
t.skip("circuits/sealed_sale_release.circom not present yet (SaleCircuit in flight)");
|
|
662
|
+
return;
|
|
663
|
+
}
|
|
664
|
+
const src = readFileSync(CIRCUIT_SRC, "utf8").replace(/\/\/[^\n]*/g, "");
|
|
665
|
+
// Scope to the main template body: helper templates (DualMux/PoseidonN/
|
|
666
|
+
// chunk fold) declare their own generic `signal input`s that are not part
|
|
667
|
+
// of the witness contract. The main template is the last one before
|
|
668
|
+
// `component main`.
|
|
669
|
+
const mainIdx = src.indexOf("component main");
|
|
670
|
+
assert.ok(mainIdx !== -1, "component main not found");
|
|
671
|
+
const tplStart = src.lastIndexOf("template ", mainIdx);
|
|
672
|
+
assert.ok(tplStart !== -1, "main template not found");
|
|
673
|
+
const tpl = src.slice(tplStart, mainIdx);
|
|
674
|
+
const declared = new Set();
|
|
675
|
+
for (const m of tpl.matchAll(/signal\s+input\s+(\w+)/g)) declared.add(m[1]);
|
|
676
|
+
assert.ok(declared.size > 0, "no `signal input` declarations found in the circuit");
|
|
677
|
+
assert.deepEqual(
|
|
678
|
+
[...declared].sort(),
|
|
679
|
+
[...EXPECTED_SIGNALS].sort(),
|
|
680
|
+
"circuit signal inputs diverge from sale-witness.mjs emission",
|
|
681
|
+
);
|
|
682
|
+
// public order in component main
|
|
683
|
+
const mainMatch = src.match(/component\s+main\s*\{[^}]*public\s*\[([^\]]*)\]/s);
|
|
684
|
+
assert.ok(mainMatch, "component main {public [...]} not found");
|
|
685
|
+
const pub = mainMatch[1].split(",").map((s) => s.trim());
|
|
686
|
+
assert.deepEqual(pub, [...SALE_PUBLIC_SIGNALS], "public input order diverges");
|
|
687
|
+
});
|
|
688
|
+
|
|
689
|
+
// ---------------------------------------------------------------------------
|
|
690
|
+
// 9. WASM-gated circuit satisfaction legs
|
|
691
|
+
// ---------------------------------------------------------------------------
|
|
692
|
+
const POLL_MS = Number(process.env.SALE_WITNESS_POLL_MS ?? 20 * 60 * 1000);
|
|
693
|
+
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
694
|
+
|
|
695
|
+
let wasmReady = existsSync(WASM) && existsSync(WITNESS_CALC);
|
|
696
|
+
if (!wasmReady && POLL_MS > 0) {
|
|
697
|
+
const deadline = Date.now() + POLL_MS;
|
|
698
|
+
while (Date.now() < deadline) {
|
|
699
|
+
await sleep(15_000);
|
|
700
|
+
if (existsSync(WASM) && existsSync(WITNESS_CALC)) {
|
|
701
|
+
wasmReady = true;
|
|
702
|
+
break;
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
if (!wasmReady) {
|
|
707
|
+
console.log(
|
|
708
|
+
`[sale-witness] sealed_sale_release wasm artifacts absent after polling ` +
|
|
709
|
+
`(${WASM}); circuit-satisfaction legs will SKIP.`,
|
|
710
|
+
);
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
/** `snarkjs wtns check` → true if the witness satisfies the R1CS. */
|
|
714
|
+
function wtnsCheck(wtnsPath) {
|
|
715
|
+
try {
|
|
716
|
+
execFileSync("npx", ["snarkjs", "wtns", "check", R1CS, wtnsPath], {
|
|
717
|
+
shell: true,
|
|
718
|
+
stdio: "pipe",
|
|
719
|
+
cwd: PKG_ROOT,
|
|
720
|
+
});
|
|
721
|
+
return true;
|
|
722
|
+
} catch {
|
|
723
|
+
return false;
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
test("compiled circuit accepts a valid sale witness", async (t) => {
|
|
728
|
+
if (!wasmReady) {
|
|
729
|
+
t.skip("sealed_sale_release wasm absent (see log above); nothing to check against");
|
|
730
|
+
return;
|
|
731
|
+
}
|
|
732
|
+
const tmp = mkdtempSync(join(tmpdir(), "enigma-sale-"));
|
|
733
|
+
t.after(() => rmSync(tmp, { recursive: true, force: true }));
|
|
734
|
+
|
|
735
|
+
const { args } = makeFixture();
|
|
736
|
+
const { input } = buildSaleWitness(args);
|
|
737
|
+
|
|
738
|
+
const buildCalc = require(WITNESS_CALC);
|
|
739
|
+
const calc = await buildCalc(readFileSync(WASM));
|
|
740
|
+
// sanityCheck=true: the calculator enforces every constraint (asserts).
|
|
741
|
+
const bin = Buffer.from(await calc.calculateWTNSBin(input, true));
|
|
742
|
+
const wtnsPath = join(tmp, "sale.wtns");
|
|
743
|
+
writeFileSync(wtnsPath, bin);
|
|
744
|
+
|
|
745
|
+
if (existsSync(R1CS)) {
|
|
746
|
+
assert.ok(wtnsCheck(wtnsPath), "valid sale witness must satisfy the R1CS");
|
|
747
|
+
}
|
|
748
|
+
});
|
|
749
|
+
|
|
750
|
+
test("compiled circuit rejects a junk-ciphertext witness", async (t) => {
|
|
751
|
+
if (!wasmReady) {
|
|
752
|
+
t.skip("sealed_sale_release wasm absent (see log above); nothing to check against");
|
|
753
|
+
return;
|
|
754
|
+
}
|
|
755
|
+
const { args } = makeFixture();
|
|
756
|
+
const { input } = buildSaleWitness(args);
|
|
757
|
+
const buildCalc = require(WITNESS_CALC);
|
|
758
|
+
const calc = await buildCalc(readFileSync(WASM));
|
|
759
|
+
|
|
760
|
+
// Tamper one ciphertext chunk WITHOUT touching the plaintext: the circuit
|
|
761
|
+
// constraint c_i - ks_i === p_i (and the cipherCommit fold) must fail —
|
|
762
|
+
// exactly what a seller would try if delivering junk bytes while claiming
|
|
763
|
+
// the listed payloadCommitPos.
|
|
764
|
+
const junk = { ...input };
|
|
765
|
+
junk.ciphertext = [...input.ciphertext];
|
|
766
|
+
junk.ciphertext[0] = ((BigInt(input.ciphertext[0]) + 1n) % R).toString();
|
|
767
|
+
await assert.rejects(
|
|
768
|
+
() => calc.calculateWTNSBin(junk, true),
|
|
769
|
+
/Assert Failed|Error/,
|
|
770
|
+
"circuit must reject ciphertext inconsistent with the committed plaintext",
|
|
771
|
+
);
|
|
772
|
+
|
|
773
|
+
// And the honest plaintext fold bound into the note must match the listed
|
|
774
|
+
// payloadCommitPos: tampering the plaintext breaks BOTH the note opening
|
|
775
|
+
// and the encryption-consistency legs.
|
|
776
|
+
const junkPt = { ...input };
|
|
777
|
+
junkPt.plaintext = [...input.plaintext];
|
|
778
|
+
junkPt.plaintext[0] = ((BigInt(input.plaintext[0]) + 1n) % R).toString();
|
|
779
|
+
await assert.rejects(
|
|
780
|
+
() => calc.calculateWTNSBin(junkPt, true),
|
|
781
|
+
/Assert Failed|Error/,
|
|
782
|
+
"circuit must reject plaintext inconsistent with the note/ciphertext",
|
|
783
|
+
);
|
|
784
|
+
});
|