enigma-memory 0.1.17 → 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 +85 -27
- package/apps/cli/bin/enigma-desktop.mjs +140 -0
- package/apps/cli/bin/enigma-terminal.mjs +78 -0
- package/apps/cli/bin/enigma.mjs +4979 -3263
- 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/SIMULATION.md +14 -9
- package/deploy/docker-compose.local-production-simulation.yml +54 -12
- package/docs/benchmark-attestation-network.md +487 -487
- package/docs/benchmark-reproducibility.md +289 -289
- package/docs/blockchain-only-mechanisms.md +400 -400
- package/docs/browser-extension-install.md +8 -6
- package/docs/client-connectors.md +16 -12
- package/docs/demo-proof-network.md +275 -275
- package/docs/developer-ecosystem.md +15 -13
- package/docs/developer-proof-quickstart.md +325 -325
- package/docs/enigma-memory-ready-conformance.md +378 -376
- package/docs/install-anywhere.md +64 -30
- package/docs/installers-and-desktop.md +8 -7
- package/docs/memory-benchmarks.md +1 -1
- package/docs/memory-drive-health-model.md +690 -690
- package/docs/novelty-invention-candidates.md +161 -161
- package/docs/proof-network-build-notes.md +240 -240
- package/docs/proof-network-claim-boundaries.md +320 -318
- package/docs/proof-network.md +339 -339
- package/docs/sdk-api.md +324 -324
- package/docs/solana-proof-rail.md +453 -453
- 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 +410 -278
- 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 +249 -2
- 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 +2498 -1185
- 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 +436 -7
- 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 +1150 -125
- 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 +292 -274
- package/scripts/build-hosted-customer-lifecycle.mjs +493 -476
- package/scripts/build-hosted-probe-worker.mjs +19 -4
- package/scripts/build-installer-assets.mjs +409 -389
- 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 +4 -1
- package/scripts/build-proof-network-packet.mjs +213 -213
- package/scripts/check.mjs +25 -4
- package/scripts/collect-hosted-backend-live-evidence.mjs +49 -12
- package/scripts/install-enigma-local.mjs +18 -5
- package/scripts/release-audit.mjs +74 -115
- package/scripts/release-provenance.mjs +12 -2
- package/scripts/run-backend-readiness-smoke.mjs +112 -10
- package/scripts/run-standard-memory-benchmarks.mjs +1354 -1352
- package/scripts/scan-secrets.mjs +178 -0
- package/scripts/simulate-production-env.mjs +71 -10
- 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,311 @@
|
|
|
1
|
+
// Enigma Encrypted Mesh Protocol ("GhostMesh") — Epidemic Gossip & Node Coordinator.
|
|
2
|
+
// Connects transports, routers, and cryptographic envelopes into an autonomous AI memory mesh node.
|
|
3
|
+
import { EventEmitter } from 'node:events';
|
|
4
|
+
import { PACKET_TYPES, encodePacket, decodePacket, deriveDestination } from './protocol.js';
|
|
5
|
+
import { generateMeshIdentity, sealPayload, unsealPayload, signPacket, verifyPacketSignature } from './crypto.js';
|
|
6
|
+
import { PacketRouter } from './router.js';
|
|
7
|
+
const MAX_DISCOVERED_PEERS = 1_024;
|
|
8
|
+
|
|
9
|
+
export class MeshGossipNode extends EventEmitter {
|
|
10
|
+
/**
|
|
11
|
+
* @param {Object} options
|
|
12
|
+
* @param {Object} [options.identity] - Ed25519 identity keypair
|
|
13
|
+
* @param {string} [options.nodeId] - Node identifier label
|
|
14
|
+
* @param {string} [options.scope='enigma.global']
|
|
15
|
+
* @param {Array<import('./transport.js').AbstractTransport>} [options.transports]
|
|
16
|
+
*/
|
|
17
|
+
constructor(options = {}) {
|
|
18
|
+
super();
|
|
19
|
+
this.scope = options.scope || 'enigma.global';
|
|
20
|
+
this.identity = options.identity || generateMeshIdentity(this.scope);
|
|
21
|
+
this.nodeId = options.nodeId || this.identity.destinationHex.slice(0, 12);
|
|
22
|
+
this.router = new PacketRouter({
|
|
23
|
+
nodeIdentity: this.identity,
|
|
24
|
+
nodeId: this.nodeId,
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
this.transports = new Map();
|
|
28
|
+
this.discoveredPeers = new Map();
|
|
29
|
+
if (Array.isArray(options.transports)) {
|
|
30
|
+
for (const t of options.transports) this.addTransport(t);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Bind router events
|
|
34
|
+
this.router.on('delivered', (evt) => this.handleDeliveredPacket(evt));
|
|
35
|
+
this.router.on('routed', (evt) => this.forwardPacket(evt));
|
|
36
|
+
this.router.on('announce', (evt) => this.handleAnnounce(evt));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
addTransport(transport) {
|
|
40
|
+
if ('identity' in transport && !transport.running) transport.identity = this.identity;
|
|
41
|
+
this.transports.set(transport.id, transport);
|
|
42
|
+
transport.on('packet', async ({ packetBuffer, fromPeerId, transportId }) => {
|
|
43
|
+
const admitted = this.router.registerPeer(fromPeerId, { transportId });
|
|
44
|
+
const result = admitted
|
|
45
|
+
? await this.router.handleIncomingPacket(packetBuffer, fromPeerId, transportId)
|
|
46
|
+
: { action: 'DROPPED', reason: 'peer_capacity_reached' };
|
|
47
|
+
this.emit('packet:handled', { result, fromPeerId, transportId });
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
transport.on('peer:connect', ({ peerId, publicKey, address }) => {
|
|
51
|
+
const admitted = this.router.registerPeer(peerId, { transportId: transport.id, publicKey, address });
|
|
52
|
+
if (!admitted) return;
|
|
53
|
+
this.emit('peer:connect', { peerId, publicKey, address, transportId: transport.id });
|
|
54
|
+
// Announce immediately to the newly connected peer
|
|
55
|
+
this.announce().catch(() => {});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
transport.on('peer:disconnect', ({ peerId }) => {
|
|
59
|
+
this.router.unregisterPeer(peerId);
|
|
60
|
+
this.emit('peer:disconnect', { peerId, transportId: transport.id });
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async start() {
|
|
65
|
+
for (const t of this.transports.values()) {
|
|
66
|
+
if (!t.running) await t.start();
|
|
67
|
+
}
|
|
68
|
+
await this.announce();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async stop() {
|
|
72
|
+
for (const t of this.transports.values()) {
|
|
73
|
+
if (t.running) await t.stop();
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Broadcasts a signed Announce beacon to all connected peers across all transports.
|
|
79
|
+
* @param {Object} [metadata] - Optional public node metadata (e.g. supported scopes, note root)
|
|
80
|
+
* @returns {Promise<Buffer>} The signed announce packet buffer
|
|
81
|
+
*/
|
|
82
|
+
async announce(metadata = {}) {
|
|
83
|
+
const metaJson = JSON.stringify({
|
|
84
|
+
nodeId: this.nodeId,
|
|
85
|
+
scope: this.scope,
|
|
86
|
+
destinationHex: this.identity.destinationHex,
|
|
87
|
+
encryptionPublicKeyHex: this.identity.encryptionPublicKey ? this.identity.encryptionPublicKey.toString('hex') : null,
|
|
88
|
+
timestamp: Date.now(),
|
|
89
|
+
...metadata,
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
const unsigned = encodePacket({
|
|
93
|
+
type: PACKET_TYPES.ANNOUNCE,
|
|
94
|
+
destination: this.identity.destination,
|
|
95
|
+
senderPubkey: this.identity.publicKey,
|
|
96
|
+
payload: Buffer.from(metaJson, 'utf8'),
|
|
97
|
+
hopTtl: 5,
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
const signed = signPacket(unsigned, this.identity.secretKey);
|
|
101
|
+
for (const t of this.transports.values()) {
|
|
102
|
+
await t.broadcast(signed);
|
|
103
|
+
}
|
|
104
|
+
return signed;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Sends an end-to-end encrypted memory capsule to a target destination.
|
|
109
|
+
* @param {Buffer|string} destination - 32-byte destination hash or hex
|
|
110
|
+
* @param {Buffer} recipientEd25519Pub - Recipient's 32-byte Ed25519 public key
|
|
111
|
+
* @param {string|Object} memoryContent - Confidential memory payload
|
|
112
|
+
* @param {Object} [opts]
|
|
113
|
+
* @returns {Promise<{packetId: string, deliveredLocally: boolean, routed: boolean}>}
|
|
114
|
+
*/
|
|
115
|
+
async sendEncryptedCapsule(destination, recipientEd25519Pub, memoryContent, opts = {}) {
|
|
116
|
+
const destBuf = Buffer.isBuffer(destination) ? destination : Buffer.from(destination, 'hex');
|
|
117
|
+
const plainJson = typeof memoryContent === 'string' ? memoryContent : JSON.stringify(memoryContent);
|
|
118
|
+
|
|
119
|
+
// 1. Seal payload with ephemeral X25519 ECDH + AES-GCM and bind destination AAD
|
|
120
|
+
const { ciphertext, authTag, ephemeralPubkey } = sealPayload(plainJson, recipientEd25519Pub, {
|
|
121
|
+
...opts,
|
|
122
|
+
aad: destBuf,
|
|
123
|
+
});
|
|
124
|
+
// 2. Encode packet
|
|
125
|
+
const unsigned = encodePacket({
|
|
126
|
+
type: PACKET_TYPES.MEMORY_CAPSULE,
|
|
127
|
+
destination: destBuf,
|
|
128
|
+
senderPubkey: this.identity.publicKey,
|
|
129
|
+
ephemeralPubkey,
|
|
130
|
+
payload: ciphertext,
|
|
131
|
+
authTag,
|
|
132
|
+
hopTtl: opts.hopTtl || 7,
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
// 3. Sign packet with sender's Ed25519 key
|
|
136
|
+
const signed = signPacket(unsigned, this.identity.secretKey);
|
|
137
|
+
|
|
138
|
+
// 4. Check if destination is self
|
|
139
|
+
if (destBuf.equals(this.identity.destination)) {
|
|
140
|
+
const decrypted = unsealPayload(ciphertext, authTag, ephemeralPubkey, this.identity.encryptionSecretKey, {
|
|
141
|
+
...opts,
|
|
142
|
+
aad: destBuf,
|
|
143
|
+
});
|
|
144
|
+
this.emit('capsule', {
|
|
145
|
+
plaintext: decrypted.toString('utf8'),
|
|
146
|
+
senderPubkey: this.identity.publicKey,
|
|
147
|
+
destination: destBuf,
|
|
148
|
+
});
|
|
149
|
+
return { packetId: signed.subarray(signed.length - 16).toString('hex'), deliveredLocally: true, routed: false };
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// 5. Route through best transport
|
|
153
|
+
const route = this.router.getBestRoute(destBuf.toString('hex'));
|
|
154
|
+
if (route) {
|
|
155
|
+
const transport = this.transports.get(route.transportId);
|
|
156
|
+
if (transport) {
|
|
157
|
+
await transport.send(signed, route.peerId);
|
|
158
|
+
return { packetId: signed.subarray(signed.length - 16).toString('hex'), deliveredLocally: false, routed: true };
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Fallback: broadcast across all active transports
|
|
163
|
+
let broadcastSent = false;
|
|
164
|
+
for (const t of this.transports.values()) {
|
|
165
|
+
const ok = await t.broadcast(signed);
|
|
166
|
+
if (ok) broadcastSent = true;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return { packetId: signed.subarray(signed.length - 16).toString('hex'), deliveredLocally: false, routed: broadcastSent };
|
|
170
|
+
}
|
|
171
|
+
handleAnnounce({ packet, metadata, fromPeerId }) {
|
|
172
|
+
const destinationHex = packet.destinationHex;
|
|
173
|
+
const publicKeyHex = packet.senderPubkey.toString('hex');
|
|
174
|
+
const encryptionPublicKeyHex = metadata.encryptionPublicKeyHex;
|
|
175
|
+
if (!this.discoveredPeers.has(destinationHex) && this.discoveredPeers.size >= MAX_DISCOVERED_PEERS) {
|
|
176
|
+
const oldest = this.discoveredPeers.keys().next().value;
|
|
177
|
+
this.discoveredPeers.delete(oldest);
|
|
178
|
+
}
|
|
179
|
+
this.discoveredPeers.set(destinationHex, {
|
|
180
|
+
destinationHex,
|
|
181
|
+
publicKeyHex,
|
|
182
|
+
encryptionPublicKeyHex,
|
|
183
|
+
agentName: metadata.agentName || metadata.name || `Node ${destinationHex.slice(0, 8)}`,
|
|
184
|
+
nodeId: metadata.nodeId || destinationHex.slice(0, 8),
|
|
185
|
+
scope: metadata.scope,
|
|
186
|
+
lastSeenAt: new Date().toISOString(),
|
|
187
|
+
metadata,
|
|
188
|
+
});
|
|
189
|
+
this.emit('peer:announce', { destinationHex, publicKeyHex, encryptionPublicKeyHex, metadata, fromPeerId });
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
getPeers() {
|
|
193
|
+
return Array.from(this.discoveredPeers.values());
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Gossips a pre-signed Solana ZK proof or transaction to mesh gateway nodes.
|
|
198
|
+
* @param {Object} proofPayload - { signature, publicInputs, proofBytes, txBase64 }
|
|
199
|
+
* @returns {Promise<Buffer>}
|
|
200
|
+
*/
|
|
201
|
+
async broadcastZkProof(proofPayload) {
|
|
202
|
+
const payloadJson = JSON.stringify(proofPayload);
|
|
203
|
+
const unsigned = encodePacket({
|
|
204
|
+
type: PACKET_TYPES.ZK_PROOF_GOSSIP,
|
|
205
|
+
destination: Buffer.alloc(32, 0xff), // Broadcast destination
|
|
206
|
+
senderPubkey: this.identity.publicKey,
|
|
207
|
+
payload: Buffer.from(payloadJson, 'utf8'),
|
|
208
|
+
hopTtl: 6,
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
const signed = signPacket(unsigned, this.identity.secretKey);
|
|
212
|
+
for (const t of this.transports.values()) {
|
|
213
|
+
await t.broadcast(signed);
|
|
214
|
+
}
|
|
215
|
+
return signed;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Gossips a Sealed Sale offer across the mesh for zero-trust buyer discovery.
|
|
220
|
+
* @param {Object} saleListing - Standard Sealed Sale listing JSON
|
|
221
|
+
* @returns {Promise<Buffer>}
|
|
222
|
+
*/
|
|
223
|
+
async broadcastSealedSaleOffer(saleListing) {
|
|
224
|
+
const listingJson = JSON.stringify(saleListing);
|
|
225
|
+
const unsigned = encodePacket({
|
|
226
|
+
type: PACKET_TYPES.SEALED_SALE_OFFER,
|
|
227
|
+
destination: Buffer.alloc(32, 0xff),
|
|
228
|
+
senderPubkey: this.identity.publicKey,
|
|
229
|
+
payload: Buffer.from(listingJson, 'utf8'),
|
|
230
|
+
hopTtl: 6,
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
const signed = signPacket(unsigned, this.identity.secretKey);
|
|
234
|
+
for (const t of this.transports.values()) {
|
|
235
|
+
await t.broadcast(signed);
|
|
236
|
+
}
|
|
237
|
+
return signed;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
handleDeliveredPacket({ packet, fromPeerId }) {
|
|
241
|
+
switch (packet.type) {
|
|
242
|
+
case PACKET_TYPES.MEMORY_CAPSULE: {
|
|
243
|
+
try {
|
|
244
|
+
const decrypted = unsealPayload(
|
|
245
|
+
packet.payload,
|
|
246
|
+
packet.authTag,
|
|
247
|
+
packet.ephemeralPubkey,
|
|
248
|
+
this.identity.encryptionSecretKey,
|
|
249
|
+
{ aad: packet.destination }
|
|
250
|
+
);
|
|
251
|
+
this.emit('capsule', {
|
|
252
|
+
plaintext: decrypted.toString('utf8'),
|
|
253
|
+
senderPubkey: packet.senderPubkey,
|
|
254
|
+
senderPubkeyHex: packet.senderPubkeyHex,
|
|
255
|
+
destinationHex: packet.destinationHex,
|
|
256
|
+
fromPeerId,
|
|
257
|
+
});
|
|
258
|
+
} catch (err) {
|
|
259
|
+
this.emit('capsule:error', { error: err.message, fromPeerId });
|
|
260
|
+
}
|
|
261
|
+
break;
|
|
262
|
+
}
|
|
263
|
+
case PACKET_TYPES.ZK_PROOF_GOSSIP: {
|
|
264
|
+
try {
|
|
265
|
+
const data = JSON.parse(packet.payload.toString('utf8'));
|
|
266
|
+
this.emit('zk:proof', { proof: data, senderPubkey: packet.senderPubkeyHex, fromPeerId });
|
|
267
|
+
} catch {}
|
|
268
|
+
break;
|
|
269
|
+
}
|
|
270
|
+
case PACKET_TYPES.SEALED_SALE_OFFER: {
|
|
271
|
+
try {
|
|
272
|
+
const data = JSON.parse(packet.payload.toString('utf8'));
|
|
273
|
+
this.emit('sale:offer', { listing: data, senderPubkey: packet.senderPubkeyHex, fromPeerId });
|
|
274
|
+
} catch {}
|
|
275
|
+
break;
|
|
276
|
+
}
|
|
277
|
+
case PACKET_TYPES.ANNOUNCE: {
|
|
278
|
+
try {
|
|
279
|
+
const data = JSON.parse(packet.payload.toString('utf8'));
|
|
280
|
+
this.emit('announce', { metadata: data, senderPubkey: packet.senderPubkeyHex, fromPeerId });
|
|
281
|
+
} catch {}
|
|
282
|
+
break;
|
|
283
|
+
}
|
|
284
|
+
default:
|
|
285
|
+
this.emit('packet', { packet, fromPeerId });
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
async forwardPacket({ packet, nextHopPeerId, transportId, forwardBuffer }) {
|
|
290
|
+
const transport = this.transports.get(transportId);
|
|
291
|
+
if (transport) {
|
|
292
|
+
await transport.send(forwardBuffer, nextHopPeerId);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
getStatus() {
|
|
297
|
+
const stats = this.router.getRoutingStats();
|
|
298
|
+
const transportStats = Array.from(this.transports.values()).map((t) => (t.getStats ? t.getStats() : { id: t.id }));
|
|
299
|
+
|
|
300
|
+
return {
|
|
301
|
+
ok: true,
|
|
302
|
+
nodeId: this.nodeId,
|
|
303
|
+
scope: this.scope,
|
|
304
|
+
destinationHex: this.identity.destinationHex,
|
|
305
|
+
publicKeyHex: this.identity.publicKey.toString('hex'),
|
|
306
|
+
encryptionPublicKeyHex: this.identity.encryptionPublicKey ? this.identity.encryptionPublicKey.toString('hex') : null,
|
|
307
|
+
router: stats,
|
|
308
|
+
transports: transportStats,
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
}
|
|
@@ -529,3 +529,9 @@ export default {
|
|
|
529
529
|
verifyFederationGrant,
|
|
530
530
|
runMeshDemo
|
|
531
531
|
};
|
|
532
|
+
export * from './protocol.js';
|
|
533
|
+
export * from './crypto.js';
|
|
534
|
+
export * from './router.js';
|
|
535
|
+
export * from './transport.js';
|
|
536
|
+
export * from './gossip.js';
|
|
537
|
+
export * from './federation-packets.js';
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
// Enigma Encrypted Mesh Protocol ("GhostMesh") — Binary Packet Framing & Codec.
|
|
2
|
+
// Implements identity-based destination derivation, binary packet serialization,
|
|
3
|
+
// and zero-copy header parsing for low-bandwidth BLE / LoRa / WebRTC transports.
|
|
4
|
+
import crypto from 'node:crypto';
|
|
5
|
+
export const PROTOCOL_MAGIC = 0x454e; // 'EN'
|
|
6
|
+
export const PROTOCOL_VERSION = 1;
|
|
7
|
+
export const HEADER_SIZE = 112; // Fixed header bytes before variable payload
|
|
8
|
+
export const MAX_PACKET_SIZE = 65535; // 64KB max packet size
|
|
9
|
+
export const DEFAULT_HOP_TTL = 7;
|
|
10
|
+
|
|
11
|
+
export const PACKET_TYPES = Object.freeze({
|
|
12
|
+
ANNOUNCE: 0x01,
|
|
13
|
+
MEMORY_CAPSULE: 0x02,
|
|
14
|
+
ZK_PROOF_GOSSIP: 0x03,
|
|
15
|
+
SEALED_SALE_OFFER: 0x04,
|
|
16
|
+
TREE_SYNC_REQ: 0x05,
|
|
17
|
+
TREE_SYNC_RESP: 0x06,
|
|
18
|
+
STORE_FORWARD_CHUNK: 0x07,
|
|
19
|
+
ACK: 0x08,
|
|
20
|
+
CAPABILITY_GRANT: 0x09,
|
|
21
|
+
FEDERATED_QUERY_REQUEST: 0x0a,
|
|
22
|
+
FEDERATED_QUERY_RESPONSE: 0x0b,
|
|
23
|
+
CAPABILITY_REVOCATION: 0x0c,
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
export const PACKET_TYPE_NAMES = Object.freeze(
|
|
27
|
+
Object.fromEntries(Object.entries(PACKET_TYPES).map(([k, v]) => [v, k]))
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Derives a 32-byte cryptographic destination hash from an agent's signing key, encryption key, and scope.
|
|
32
|
+
* @param {Buffer|Uint8Array|string} ed25519PublicKey - 32-byte Ed25519 public key
|
|
33
|
+
* @param {Buffer|Uint8Array|string|{x: string|bigint, y: string|bigint}|null} [encryptionPublicKey] - 32-byte X25519 public key or BabyJub point
|
|
34
|
+
* @param {string} [scope='enigma.global']
|
|
35
|
+
* @returns {Buffer} 32-byte destination hash
|
|
36
|
+
*/
|
|
37
|
+
export function deriveDestination(ed25519PublicKey, encryptionPublicKey = null, scope = 'enigma.global') {
|
|
38
|
+
const h = crypto.createHash('sha256');
|
|
39
|
+
const pubBuf = Buffer.isBuffer(ed25519PublicKey)
|
|
40
|
+
? ed25519PublicKey
|
|
41
|
+
: typeof ed25519PublicKey === 'string'
|
|
42
|
+
? Buffer.from(ed25519PublicKey, 'hex')
|
|
43
|
+
: Buffer.from(ed25519PublicKey);
|
|
44
|
+
h.update(pubBuf);
|
|
45
|
+
|
|
46
|
+
if (encryptionPublicKey) {
|
|
47
|
+
if (Buffer.isBuffer(encryptionPublicKey)) {
|
|
48
|
+
h.update(encryptionPublicKey);
|
|
49
|
+
} else if (typeof encryptionPublicKey === 'string') {
|
|
50
|
+
h.update(Buffer.from(encryptionPublicKey, 'hex'));
|
|
51
|
+
} else if (encryptionPublicKey instanceof Uint8Array) {
|
|
52
|
+
h.update(Buffer.from(encryptionPublicKey));
|
|
53
|
+
} else if (encryptionPublicKey.x && encryptionPublicKey.y) {
|
|
54
|
+
const xHex = BigInt(encryptionPublicKey.x).toString(16).padStart(64, '0');
|
|
55
|
+
const yHex = BigInt(encryptionPublicKey.y).toString(16).padStart(64, '0');
|
|
56
|
+
h.update(Buffer.from(xHex, 'hex'));
|
|
57
|
+
h.update(Buffer.from(yHex, 'hex'));
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
h.update(Buffer.from(scope, 'utf8'));
|
|
62
|
+
return h.digest();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Encodes a structured GhostMesh packet into a raw binary buffer.
|
|
67
|
+
* @param {Object} p
|
|
68
|
+
* @param {number} p.type - Packet type (PACKET_TYPES)
|
|
69
|
+
* @param {Buffer} p.destination - 32-byte destination hash
|
|
70
|
+
* @param {Buffer} p.senderPubkey - 32-byte Ed25519 sender public key
|
|
71
|
+
* @param {Buffer} [p.ephemeralPubkey] - 32-byte X25519 / BabyJub ephemeral key or IV
|
|
72
|
+
* @param {Buffer} p.payload - Raw or encrypted payload buffer
|
|
73
|
+
* @param {Buffer} [p.authTag] - 16-byte Poly1305 / GCM authentication tag
|
|
74
|
+
* @param {Buffer} [p.signature] - 64-byte Ed25519 signature
|
|
75
|
+
* @param {number} [p.hopTtl=DEFAULT_HOP_TTL]
|
|
76
|
+
* @param {number} [p.seqId=0]
|
|
77
|
+
* @param {number} [p.chunkIndex=0]
|
|
78
|
+
* @param {number} [p.chunkTotal=1]
|
|
79
|
+
* @param {number} [p.flags=0]
|
|
80
|
+
* @returns {Buffer}
|
|
81
|
+
*/
|
|
82
|
+
export function encodePacket(p) {
|
|
83
|
+
if (!p || typeof p !== 'object') throw new Error('Packet must be an object');
|
|
84
|
+
const payload = Buffer.isBuffer(p.payload) ? p.payload : Buffer.from(p.payload || []);
|
|
85
|
+
if (payload.length > MAX_PACKET_SIZE - HEADER_SIZE - 64 - 16) {
|
|
86
|
+
throw new Error(`Payload size (${payload.length}B) exceeds max packet limit`);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const type = p.type === undefined ? PACKET_TYPES.MEMORY_CAPSULE : p.type;
|
|
90
|
+
if (!Number.isInteger(type) || !PACKET_TYPE_NAMES[type]) {
|
|
91
|
+
throw new Error(`Unknown packet type: ${type}`);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const byteField = (value, fallback, name) => {
|
|
95
|
+
const normalized = value === undefined ? fallback : value;
|
|
96
|
+
if (!Number.isInteger(normalized) || normalized < 0 || normalized > 0xff) {
|
|
97
|
+
throw new Error(`${name} must be an integer between 0 and 255`);
|
|
98
|
+
}
|
|
99
|
+
return normalized;
|
|
100
|
+
};
|
|
101
|
+
const uint16Field = (value, fallback, name) => {
|
|
102
|
+
const normalized = value === undefined ? fallback : value;
|
|
103
|
+
if (!Number.isInteger(normalized) || normalized < 0 || normalized > 0xffff) {
|
|
104
|
+
throw new Error(`${name} must be an integer between 0 and 65535`);
|
|
105
|
+
}
|
|
106
|
+
return normalized;
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const flags = byteField(p.flags, 0, 'flags');
|
|
110
|
+
const hopTtl = byteField(p.hopTtl, DEFAULT_HOP_TTL, 'hopTtl');
|
|
111
|
+
const seqId = uint16Field(p.seqId, 0, 'seqId');
|
|
112
|
+
const chunkIndex = uint16Field(p.chunkIndex, 0, 'chunkIndex');
|
|
113
|
+
const chunkTotal = uint16Field(p.chunkTotal, 1, 'chunkTotal');
|
|
114
|
+
if (chunkTotal === 0) throw new Error('chunkTotal must be at least 1');
|
|
115
|
+
if (chunkIndex >= chunkTotal) {
|
|
116
|
+
throw new Error(`chunkIndex ${chunkIndex} must be less than chunkTotal ${chunkTotal}`);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const destination = Buffer.from(p.destination || []);
|
|
120
|
+
if (destination.length !== 32) throw new Error('Destination must be a 32-byte buffer');
|
|
121
|
+
|
|
122
|
+
const senderPubkey = Buffer.from(p.senderPubkey || []);
|
|
123
|
+
if (senderPubkey.length !== 32) throw new Error('Sender public key must be a 32-byte buffer');
|
|
124
|
+
|
|
125
|
+
const ephemeralPubkey = p.ephemeralPubkey ? Buffer.from(p.ephemeralPubkey) : Buffer.alloc(32, 0);
|
|
126
|
+
if (ephemeralPubkey.length !== 32) throw new Error('Ephemeral public key must be 32 bytes');
|
|
127
|
+
|
|
128
|
+
const authTag = p.authTag ? Buffer.from(p.authTag) : Buffer.alloc(16, 0);
|
|
129
|
+
if (authTag.length !== 16) throw new Error('Auth tag must be 16 bytes');
|
|
130
|
+
|
|
131
|
+
const signature = p.signature ? Buffer.from(p.signature) : Buffer.alloc(64, 0);
|
|
132
|
+
if (signature.length !== 64) throw new Error('Signature must be 64 bytes');
|
|
133
|
+
|
|
134
|
+
const totalLength = HEADER_SIZE + payload.length + 16 + 64;
|
|
135
|
+
if (totalLength > MAX_PACKET_SIZE) throw new Error(`Packet size (${totalLength}B) exceeds max packet limit`);
|
|
136
|
+
const buf = Buffer.alloc(totalLength);
|
|
137
|
+
|
|
138
|
+
let offset = 0;
|
|
139
|
+
buf.writeUInt16BE(PROTOCOL_MAGIC, offset); offset += 2;
|
|
140
|
+
buf.writeUInt8(PROTOCOL_VERSION, offset); offset += 1;
|
|
141
|
+
buf.writeUInt8(type, offset); offset += 1;
|
|
142
|
+
buf.writeUInt8(flags, offset); offset += 1;
|
|
143
|
+
buf.writeUInt8(hopTtl, offset); offset += 1;
|
|
144
|
+
buf.writeUInt16BE(seqId, offset); offset += 2;
|
|
145
|
+
|
|
146
|
+
destination.copy(buf, offset); offset += 32;
|
|
147
|
+
senderPubkey.copy(buf, offset); offset += 32;
|
|
148
|
+
ephemeralPubkey.copy(buf, offset); offset += 32;
|
|
149
|
+
|
|
150
|
+
buf.writeUInt32BE(payload.length, offset); offset += 4;
|
|
151
|
+
buf.writeUInt16BE(chunkIndex, offset); offset += 2;
|
|
152
|
+
buf.writeUInt16BE(chunkTotal, offset); offset += 2;
|
|
153
|
+
|
|
154
|
+
payload.copy(buf, offset); offset += payload.length;
|
|
155
|
+
authTag.copy(buf, offset); offset += 16;
|
|
156
|
+
signature.copy(buf, offset);
|
|
157
|
+
|
|
158
|
+
return buf;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Decodes a raw binary buffer into a structured GhostMesh packet object.
|
|
163
|
+
* @param {Buffer|Uint8Array} rawBuffer
|
|
164
|
+
* @returns {Object} Decoded packet structure
|
|
165
|
+
*/
|
|
166
|
+
export function decodePacket(rawBuffer) {
|
|
167
|
+
const buf = Buffer.isBuffer(rawBuffer) ? rawBuffer : Buffer.from(rawBuffer);
|
|
168
|
+
if (buf.length < HEADER_SIZE + 16 + 64) {
|
|
169
|
+
throw new Error(`Packet buffer too short (${buf.length} bytes; minimum is ${HEADER_SIZE + 80} bytes)`);
|
|
170
|
+
}
|
|
171
|
+
if (buf.length > MAX_PACKET_SIZE) {
|
|
172
|
+
throw new Error(`Packet buffer too large (${buf.length} bytes; maximum is ${MAX_PACKET_SIZE} bytes)`);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
let offset = 0;
|
|
176
|
+
const magic = buf.readUInt16BE(offset); offset += 2;
|
|
177
|
+
if (magic !== PROTOCOL_MAGIC) {
|
|
178
|
+
throw new Error(`Invalid protocol magic: 0x${magic.toString(16)} (expected 0x${PROTOCOL_MAGIC.toString(16)})`);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const version = buf.readUInt8(offset); offset += 1;
|
|
182
|
+
if (version !== PROTOCOL_VERSION) {
|
|
183
|
+
throw new Error(`Unsupported protocol version: ${version} (expected ${PROTOCOL_VERSION})`);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const type = buf.readUInt8(offset); offset += 1;
|
|
187
|
+
if (!PACKET_TYPE_NAMES[type]) throw new Error(`Unknown packet type: ${type}`);
|
|
188
|
+
const flags = buf.readUInt8(offset); offset += 1;
|
|
189
|
+
const hopTtl = buf.readUInt8(offset); offset += 1;
|
|
190
|
+
const seqId = buf.readUInt16BE(offset); offset += 2;
|
|
191
|
+
|
|
192
|
+
const destination = buf.subarray(offset, offset + 32); offset += 32;
|
|
193
|
+
const senderPubkey = buf.subarray(offset, offset + 32); offset += 32;
|
|
194
|
+
const ephemeralPubkey = buf.subarray(offset, offset + 32); offset += 32;
|
|
195
|
+
|
|
196
|
+
const payloadLength = buf.readUInt32BE(offset); offset += 4;
|
|
197
|
+
const chunkIndex = buf.readUInt16BE(offset); offset += 2;
|
|
198
|
+
const chunkTotal = buf.readUInt16BE(offset); offset += 2;
|
|
199
|
+
if (chunkTotal === 0) throw new Error('Invalid packet chunks: chunkTotal must be at least 1');
|
|
200
|
+
if (chunkIndex >= chunkTotal) {
|
|
201
|
+
throw new Error(`Invalid packet chunks: chunkIndex ${chunkIndex} must be less than chunkTotal ${chunkTotal}`);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const expectedLength = HEADER_SIZE + payloadLength + 16 + 64;
|
|
205
|
+
if (expectedLength !== buf.length) {
|
|
206
|
+
if (expectedLength > buf.length) {
|
|
207
|
+
throw new Error(`Packet payload truncated (expected exactly ${expectedLength} bytes, got ${buf.length})`);
|
|
208
|
+
}
|
|
209
|
+
throw new Error(`Packet frame has trailing bytes (expected exactly ${expectedLength} bytes, got ${buf.length})`);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const payload = buf.subarray(offset, offset + payloadLength); offset += payloadLength;
|
|
213
|
+
const authTag = buf.subarray(offset, offset + 16); offset += 16;
|
|
214
|
+
const signature = buf.subarray(offset, offset + 64);
|
|
215
|
+
|
|
216
|
+
return {
|
|
217
|
+
magic,
|
|
218
|
+
version,
|
|
219
|
+
type,
|
|
220
|
+
typeName: PACKET_TYPE_NAMES[type],
|
|
221
|
+
flags,
|
|
222
|
+
hopTtl,
|
|
223
|
+
seqId,
|
|
224
|
+
destination,
|
|
225
|
+
destinationHex: destination.toString('hex'),
|
|
226
|
+
senderPubkey,
|
|
227
|
+
senderPubkeyHex: senderPubkey.toString('hex'),
|
|
228
|
+
ephemeralPubkey,
|
|
229
|
+
ephemeralPubkeyHex: ephemeralPubkey.toString('hex'),
|
|
230
|
+
payloadLength,
|
|
231
|
+
chunkIndex,
|
|
232
|
+
chunkTotal,
|
|
233
|
+
payload,
|
|
234
|
+
authTag,
|
|
235
|
+
signature,
|
|
236
|
+
rawBytes: buf,
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
export function getSignableBytes(packetBuffer) {
|
|
240
|
+
const decoded = decodePacket(packetBuffer);
|
|
241
|
+
const buf = decoded.rawBytes;
|
|
242
|
+
// TTL is the only mutable field. Relays may decrement it without invalidating
|
|
243
|
+
// the end-to-end signature; every other frame byte is immutable.
|
|
244
|
+
const prefix = buf.subarray(0, 5);
|
|
245
|
+
const remainder = buf.subarray(6, buf.length - 64);
|
|
246
|
+
return Buffer.concat([prefix, remainder]);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Stable replay identity for a packet. It excludes the mutable hop TTL and the
|
|
251
|
+
* signature bytes, so decrementing TTL cannot turn a replay into a new packet.
|
|
252
|
+
*/
|
|
253
|
+
export function getPacketReplayIdentity(packetBuffer) {
|
|
254
|
+
return crypto.createHash('sha256').update(getSignableBytes(packetBuffer)).digest('hex');
|
|
255
|
+
}
|