634 lines
18 KiB
TypeScript
634 lines
18 KiB
TypeScript
/**
|
|
* P2P Swarm v2 - Production Grade
|
|
*
|
|
* Fixes from code review:
|
|
* 1. Two-layer key scheme (swarm envelope key + per-peer session keys)
|
|
* 2. Ed25519 identity keys + X25519 ephemeral keys
|
|
* 3. Message replay protection (nonces, counters, timestamps)
|
|
* 4. Gun-based WebRTC signaling (no external PeerServer)
|
|
* 5. IPFS CID pointers for large payloads
|
|
* 6. Ed25519 signatures on all messages
|
|
* 7. Relay health monitoring
|
|
* 8. Task execution envelope with budgets
|
|
*/
|
|
/**
|
|
* Stable canonical JSON stringify
|
|
*
|
|
* Unlike JSON.stringify, this:
|
|
* - Sorts object keys recursively (alphabetically)
|
|
* - Produces identical output regardless of insertion order
|
|
* - Is safe to use for signing and hashing
|
|
*/
|
|
declare function stableStringify(obj: unknown): string;
|
|
/**
|
|
* Identity and session key management
|
|
*
|
|
* Security fixes applied:
|
|
* - Ed25519 uses crypto.sign(null, ...) - direct sign, not hash-then-sign
|
|
* - Separate send counter (local) from receive counters (per-peer)
|
|
* - Per-sender nonce tracking with timestamps for expiry
|
|
* - X25519 ECDH + HKDF for real session key derivation
|
|
*/
|
|
declare class IdentityManager {
|
|
private identityKey;
|
|
private x25519Key;
|
|
private peerSessionKeys;
|
|
private seenNonces;
|
|
private localSendCounter;
|
|
private recvCounters;
|
|
private maxNonceAge;
|
|
private nonceCleanupInterval;
|
|
constructor();
|
|
/**
|
|
* Get public identity key (PEM format for transport)
|
|
*/
|
|
getPublicKey(): string;
|
|
/**
|
|
* Get X25519 public key for key exchange
|
|
*/
|
|
getX25519PublicKey(): string;
|
|
/**
|
|
* Sign data with Ed25519 identity key
|
|
* Ed25519 uses direct sign (no hash algorithm needed)
|
|
*/
|
|
sign(data: string): string;
|
|
/**
|
|
* Verify Ed25519 signature from peer
|
|
*/
|
|
verify(data: string, signature: string, peerPublicKey: string): boolean;
|
|
/**
|
|
* Derive session key with peer using real X25519 ECDH + HKDF
|
|
*
|
|
* Salt derivation: sha256(min(pubA, pubB) || max(pubA, pubB))
|
|
* - Stable per pair regardless of who initiates
|
|
* - High entropy from public key material
|
|
*
|
|
* Info: "p2p-swarm-v2:${swarmId}:${peerId}"
|
|
* - Prevents cross-swarm key reuse
|
|
* - Makes derived keys unique per pair and swarm
|
|
*/
|
|
deriveSessionKey(peerX25519PubKey: string, peerId: string, swarmId?: string): Buffer;
|
|
/**
|
|
* Generate cryptographically secure nonce
|
|
*/
|
|
generateNonce(): string;
|
|
/**
|
|
* Check and record nonce with per-sender tracking
|
|
* Returns true if nonce is valid (not replayed, not expired)
|
|
*/
|
|
checkNonce(nonce: string, timestamp: number, senderId: string): boolean;
|
|
/**
|
|
* Periodic cleanup of expired nonces
|
|
*/
|
|
private startNonceCleanup;
|
|
/**
|
|
* Get next send counter (monotonically increasing)
|
|
*/
|
|
getNextSendCounter(): number;
|
|
/**
|
|
* Validate received counter from peer (must be > last seen from that peer)
|
|
*/
|
|
validateRecvCounter(peerId: string, counter: number): boolean;
|
|
/**
|
|
* Clear session key (for rotation)
|
|
*/
|
|
rotateSessionKey(peerId: string): void;
|
|
/**
|
|
* Clean up resources
|
|
*/
|
|
destroy(): void;
|
|
}
|
|
/**
|
|
* Encryption with proper GCM handling
|
|
*/
|
|
declare class CryptoV2 {
|
|
private algorithm;
|
|
encrypt(data: string, key: Buffer): {
|
|
ciphertext: string;
|
|
iv: string;
|
|
tag: string;
|
|
};
|
|
decrypt(ciphertext: string, key: Buffer, iv: string, tag: string): string | null;
|
|
hash(data: string | Buffer): string;
|
|
generateCID(data: string | Buffer): string;
|
|
}
|
|
/**
|
|
* Signed message envelope for Gun pubsub
|
|
*
|
|
* Design rules:
|
|
* - Gun = coordination bus (always use swarm envelope key)
|
|
* - IPFS = artifact plane (content-addressed storage)
|
|
* - WebRTC = private fast lane (can use per-peer session keys)
|
|
*
|
|
* Session keys are NOT used for Gun messages to avoid decryption
|
|
* mismatch problems where only one receiver can decrypt.
|
|
*/
|
|
interface SignedEnvelope {
|
|
messageId: string;
|
|
topic: string;
|
|
timestamp: number;
|
|
senderId: string;
|
|
senderPubKey: string;
|
|
senderX25519Key: string;
|
|
payloadHash: string;
|
|
nonce: string;
|
|
counter: number;
|
|
signature: string;
|
|
encrypted: {
|
|
ciphertext: string;
|
|
iv: string;
|
|
tag: string;
|
|
};
|
|
}
|
|
/**
|
|
* Task execution envelope
|
|
*/
|
|
interface TaskEnvelope {
|
|
taskId: string;
|
|
moduleCID: string;
|
|
entrypoint: string;
|
|
inputCID: string;
|
|
outputSchemaHash: string;
|
|
budgets: {
|
|
fuelLimit: number;
|
|
memoryMB: number;
|
|
timeoutMs: number;
|
|
};
|
|
requester: string;
|
|
deadline: number;
|
|
priority: number;
|
|
}
|
|
/**
|
|
* Task result receipt with full execution binding
|
|
* The signature covers ALL fields to prevent tampering
|
|
*
|
|
* Includes binding to the original TaskEnvelope for complete traceability
|
|
*/
|
|
interface TaskReceipt {
|
|
taskId: string;
|
|
executor: string;
|
|
executorPubKey: string;
|
|
resultCID: string;
|
|
status: 'success' | 'error' | 'timeout' | 'oom';
|
|
fuelUsed: number;
|
|
memoryPeakMB: number;
|
|
executionMs: number;
|
|
inputHash: string;
|
|
outputHash: string;
|
|
moduleHash: string;
|
|
startTimestamp: number;
|
|
endTimestamp: number;
|
|
moduleCID: string;
|
|
inputCID: string;
|
|
entrypoint: string;
|
|
outputSchemaHash: string;
|
|
taskEnvelopeHash: string;
|
|
signature: string;
|
|
}
|
|
/**
|
|
* Learning artifact pointer (small, goes to Gun)
|
|
*/
|
|
interface ArtifactPointer {
|
|
type: 'q_table' | 'memory_vectors' | 'model_weights' | 'trajectory';
|
|
agentId: string;
|
|
cid: string;
|
|
version: number;
|
|
schemaHash: string;
|
|
dimensions: string;
|
|
checksum: string;
|
|
timestamp: number;
|
|
signature: string;
|
|
}
|
|
/**
|
|
* Relay health tracker
|
|
*/
|
|
declare class RelayManager {
|
|
private relays;
|
|
private workingRelays;
|
|
static readonly BOOTSTRAP_RELAYS: string[];
|
|
constructor(customRelays?: string[]);
|
|
/**
|
|
* Get working relays
|
|
*/
|
|
getWorkingRelays(): string[];
|
|
/**
|
|
* Mark relay as failed
|
|
*/
|
|
markFailed(url: string): void;
|
|
/**
|
|
* Mark relay as successful
|
|
*/
|
|
markSuccess(url: string, latencyMs: number): void;
|
|
/**
|
|
* Add new relay
|
|
*/
|
|
addRelay(url: string): void;
|
|
/**
|
|
* Get health metrics
|
|
*/
|
|
getMetrics(): {
|
|
total: number;
|
|
healthy: number;
|
|
avgLatency: number;
|
|
};
|
|
/**
|
|
* Persist working set
|
|
*/
|
|
exportWorkingSet(): string[];
|
|
/**
|
|
* Import working set
|
|
*/
|
|
importWorkingSet(relays: string[]): void;
|
|
}
|
|
/**
|
|
* IPFS artifact manager (CID-based storage)
|
|
*
|
|
* IMPORTANT CID LIMITATION:
|
|
* The current generateCID creates a simplified hash-based identifier (Qm prefix).
|
|
* This is NOT a real IPFS CID and will NOT interop with:
|
|
* - Real IPFS gateways
|
|
* - Pinning services (Pinata, web3.storage, Filebase)
|
|
* - Content routing
|
|
*
|
|
* For production, you need one of:
|
|
* A) Real IPFS client: add bytes to IPFS node, take returned CID
|
|
* B) Multiformats library: create CIDv1 from raw bytes offline
|
|
*
|
|
* The current implementation works for local coordination and can be
|
|
* upgraded to real IPFS when needed.
|
|
*
|
|
* Gateway fetch is DISABLED by default because our fake CIDs won't work.
|
|
* Enable only when using real multiformats CIDv1 generation.
|
|
*/
|
|
declare class ArtifactStore {
|
|
private localCache;
|
|
private crypto;
|
|
private maxCacheSize;
|
|
private currentCacheSize;
|
|
private maxArtifactSize;
|
|
private enableGatewayFetch;
|
|
static readonly CHUNK_SIZE: number;
|
|
static readonly IPFS_GATEWAYS: string[];
|
|
constructor(enableGatewayFetch?: boolean);
|
|
/**
|
|
* Store artifact and get CID
|
|
*/
|
|
store(data: Buffer | string, compress?: boolean): Promise<{
|
|
cid: string;
|
|
size: number;
|
|
chunks: number;
|
|
}>;
|
|
/**
|
|
* Retrieve artifact by CID
|
|
*
|
|
* Strategy:
|
|
* 1. Check local cache
|
|
* 2. If enableGatewayFetch=true AND CID is real (bafy prefix), try gateways
|
|
* 3. Otherwise return null (local-only mode)
|
|
*
|
|
* Gateway fetch is DISABLED by default because our simplified CIDs
|
|
* are not real IPFS CIDs and won't work with gateways.
|
|
*/
|
|
retrieve(cid: string): Promise<Buffer | null>;
|
|
/**
|
|
* Enable gateway fetch (only when using real IPFS CIDs)
|
|
*/
|
|
setEnableGatewayFetch(enable: boolean): void;
|
|
/**
|
|
* Store in local cache with eviction
|
|
*/
|
|
private cacheStore;
|
|
/**
|
|
* Create artifact pointer for Gun
|
|
* Uses stableStringify for deterministic signing
|
|
*/
|
|
createPointer(type: ArtifactPointer['type'], agentId: string, cid: string, dimensions: string, identity: IdentityManager): ArtifactPointer;
|
|
}
|
|
/**
|
|
* Production-grade P2P Swarm Coordinator
|
|
*/
|
|
export declare class P2PSwarmV2 {
|
|
private identity;
|
|
private crypto;
|
|
private relayManager;
|
|
private artifactStore;
|
|
private swarmKey;
|
|
private swarmId;
|
|
private agentId;
|
|
private gun;
|
|
private swarmNode;
|
|
private connected;
|
|
private messageHandlers;
|
|
private pendingOffers;
|
|
private memberRegistry;
|
|
private heartbeatInterval;
|
|
private taskExecutorActive;
|
|
private claimedTasks;
|
|
private readonly HEARTBEAT_INTERVAL_MS;
|
|
private readonly MEMBER_TIMEOUT_MS;
|
|
private negativeMemberCache;
|
|
private readonly NEGATIVE_CACHE_TTL_MS;
|
|
private readonly CLAIM_TTL_MS;
|
|
constructor(agentId: string, swarmKey?: string);
|
|
/**
|
|
* Connect to Gun relays
|
|
*
|
|
* Starts autonomy features:
|
|
* - Membership watcher (subscribe to members, verify registrations)
|
|
* - Heartbeat publishing (every 20 seconds)
|
|
* - Task executor loop (optional, enable with startTaskExecutor())
|
|
*/
|
|
connect(): Promise<boolean>;
|
|
/**
|
|
* Register self with public key and X25519 key
|
|
* Uses canonical serialization for signature
|
|
* Field order must match verifyMemberRegistration
|
|
*/
|
|
private registerSelf;
|
|
/**
|
|
* Verified member entry type
|
|
*/
|
|
private static readonly MEMBER_REQUIRED_FIELDS;
|
|
/**
|
|
* Verify and cache a member registration from Gun
|
|
* STRICT: Requires all fields including x25519PublicKey
|
|
* Returns the verified member or null if invalid
|
|
*/
|
|
private verifyMemberRegistration;
|
|
/**
|
|
* Resolve full verified member from the registry
|
|
* Returns the complete member entry (with both Ed25519 and X25519 keys)
|
|
* Returns null if member not found or not verified
|
|
*
|
|
* Uses negative cache to prevent spam from repeated lookups of unknown agents
|
|
*/
|
|
private resolveMember;
|
|
/**
|
|
* Convenience: Resolve just the Ed25519 public key
|
|
*/
|
|
private resolveMemberKey;
|
|
/**
|
|
* Subscribe to WebRTC signaling (Gun-based, no PeerServer needed)
|
|
* SECURITY: Verifies signatures using registry key, not envelope key
|
|
*/
|
|
private subscribeToSignaling;
|
|
/**
|
|
* Handle incoming WebRTC offer (via Gun signaling)
|
|
*/
|
|
private handleOffer;
|
|
/**
|
|
* Handle incoming WebRTC answer
|
|
*/
|
|
private handleAnswer;
|
|
/**
|
|
* Handle incoming ICE candidate
|
|
*/
|
|
private handleICE;
|
|
/**
|
|
* Create canonical signaling message and signature
|
|
* Uses stableStringify for deterministic serialization
|
|
*/
|
|
private createCanonicalSignal;
|
|
/**
|
|
* Send WebRTC offer via Gun (no PeerServer needed)
|
|
* Uses canonical signature for verification
|
|
*/
|
|
sendOffer(targetAgentId: string, offer: any): Promise<void>;
|
|
/**
|
|
* Send WebRTC answer via Gun
|
|
*/
|
|
sendAnswer(targetAgentId: string, answer: any): Promise<void>;
|
|
/**
|
|
* Send ICE candidate via Gun
|
|
*/
|
|
sendICE(targetAgentId: string, candidate: any): Promise<void>;
|
|
/**
|
|
* Publish signed and encrypted message
|
|
*
|
|
* Gun messages ALWAYS use swarm envelope key (not session keys)
|
|
* This ensures all swarm members can decrypt and process messages.
|
|
* Uses canonical serialization for signature stability.
|
|
*/
|
|
publish(topic: string, payload: any): Promise<string>;
|
|
/**
|
|
* Subscribe to topic with strict identity verification
|
|
*
|
|
* SECURITY: Never trust keys from envelopes. Always resolve from registry.
|
|
*
|
|
* Verification steps:
|
|
* 1. Check nonce and timestamp (replay protection)
|
|
* 2. Check counter (ordering)
|
|
* 3. Resolve sender's public key from verified member registry
|
|
* 4. Reject if envelope key differs from registry key
|
|
* 5. Verify signature using registry key
|
|
* 6. Decrypt with swarm key
|
|
* 7. Verify payload hash
|
|
*/
|
|
subscribe(topic: string, callback: (data: any, from: string) => void): void;
|
|
/**
|
|
* Store Q-table and publish pointer
|
|
*/
|
|
syncQTable(qTable: number[][]): Promise<ArtifactPointer>;
|
|
/**
|
|
* Store memory vectors and publish pointer
|
|
*/
|
|
syncMemory(vectors: number[][], namespace: string): Promise<ArtifactPointer>;
|
|
/**
|
|
* Submit task for execution
|
|
*/
|
|
submitTask(task: Omit<TaskEnvelope, 'requester' | 'deadline' | 'priority'>): Promise<string>;
|
|
/**
|
|
* Submit task result with full execution binding
|
|
* Signs ALL fields including TaskEnvelope binding for complete traceability
|
|
*/
|
|
submitResult(receipt: Omit<TaskReceipt, 'signature' | 'executorPubKey'>): Promise<string>;
|
|
/**
|
|
* Get status
|
|
*/
|
|
getStatus(): {
|
|
connected: boolean;
|
|
swarmId: string;
|
|
agentId: string;
|
|
publicKey: string;
|
|
relays: {
|
|
total: number;
|
|
healthy: number;
|
|
avgLatency: number;
|
|
};
|
|
};
|
|
/**
|
|
* Get swarm key for sharing
|
|
*/
|
|
getSwarmKey(): string;
|
|
/**
|
|
* Start membership watcher
|
|
* Continuously monitors the members map, verifies registrations, tracks liveness
|
|
*/
|
|
private startMembershipWatcher;
|
|
/**
|
|
* Subscribe to heartbeat messages to track liveness
|
|
* Explicitly rejects agentId mismatches to prevent confusion attacks
|
|
*/
|
|
private subscribeToHeartbeats;
|
|
/**
|
|
* Start publishing heartbeats
|
|
* Every HEARTBEAT_INTERVAL_MS, publish a signed heartbeat message
|
|
*/
|
|
private startHeartbeat;
|
|
/**
|
|
* Publish a single heartbeat
|
|
* Belt-and-suspenders: check both connected and swarmNode
|
|
*/
|
|
private publishHeartbeat;
|
|
/**
|
|
* Get list of live members (heartbeat within MEMBER_TIMEOUT_MS)
|
|
*/
|
|
getLiveMembers(): Array<{
|
|
agentId: string;
|
|
capabilities: string[];
|
|
lastSeen: number;
|
|
isAlive: boolean;
|
|
}>;
|
|
/**
|
|
* Get count of live members
|
|
*/
|
|
getLiveMemberCount(): number;
|
|
/**
|
|
* Verify a task receipt
|
|
*
|
|
* Checks:
|
|
* 1. Executor is in verified member registry
|
|
* 2. Executor public key matches registry
|
|
* 3. Signature is valid over the canonical receipt fields
|
|
* 4. Optionally verifies taskEnvelopeHash if original task is provided
|
|
*/
|
|
verifyReceipt(receipt: TaskReceipt, originalTask?: TaskEnvelope): Promise<{
|
|
valid: boolean;
|
|
reason?: string;
|
|
executor?: {
|
|
agentId: string;
|
|
capabilities: string[];
|
|
verified: boolean;
|
|
};
|
|
}>;
|
|
/**
|
|
* Start the task executor loop
|
|
* Subscribes to tasks, claims them, and (in production) executes in Wasmtime
|
|
*
|
|
* Security checks:
|
|
* - Requester must be verified member
|
|
* - Requester in envelope must match sender
|
|
* - Executor must have 'executor' capability
|
|
*/
|
|
startTaskExecutor(): void;
|
|
/**
|
|
* Claim a task with conflict resolution
|
|
*
|
|
* Before claiming:
|
|
* 1. Check for existing claim
|
|
* 2. If claim exists and is fresh (< CLAIM_TTL_MS), skip
|
|
* 3. If no claim or stale, write our claim
|
|
*
|
|
* Uses single timestamp to ensure signature matches stored data
|
|
*/
|
|
private claimTask;
|
|
/**
|
|
* Execute a task
|
|
*
|
|
* NOTE: This is a stub. In production, this would:
|
|
* 1. Fetch WASM module from IPFS using moduleCID
|
|
* 2. Fetch input data from IPFS using inputCID
|
|
* 3. Execute in Wasmtime with fuel, memory, and timeout limits
|
|
* 4. Store output to IPFS, get resultCID
|
|
* 5. Return signed receipt
|
|
*
|
|
* Hashes actual bytes when available for meaningful receipts
|
|
*/
|
|
private executeTask;
|
|
/**
|
|
* Create error receipt for failed task
|
|
* Uses same hashing semantics as executeTask for consistency
|
|
*/
|
|
private createErrorReceipt;
|
|
/**
|
|
* Stop task executor
|
|
*/
|
|
stopTaskExecutor(): void;
|
|
/**
|
|
* Disconnect and cleanup resources
|
|
*/
|
|
disconnect(): void;
|
|
}
|
|
/**
|
|
* RuVector optimization for Gun message handling
|
|
*
|
|
* Provides:
|
|
* - HNSW indexing for O(log n) message lookup by topic similarity
|
|
* - Batch message operations for reduced overhead
|
|
* - PQ quantization for compressed message metadata storage
|
|
* - Vector-based topic routing for intelligent distribution
|
|
*/
|
|
declare class RuVectorGunOptimizer {
|
|
private topicVectors;
|
|
private messageIndex;
|
|
private batchQueue;
|
|
private batchFlushInterval;
|
|
private readonly batchSize;
|
|
private readonly flushIntervalMs;
|
|
constructor();
|
|
/**
|
|
* Generate topic embedding using simple hash-based vectors
|
|
* In production: use actual embedding model via ruvector
|
|
*/
|
|
private generateTopicVector;
|
|
/**
|
|
* Find similar topics using cosine similarity
|
|
* In production: use HNSW index via ruvector for O(log n) lookup
|
|
*/
|
|
findSimilarTopics(topic: string, topK?: number): Array<{
|
|
topic: string;
|
|
similarity: number;
|
|
}>;
|
|
/**
|
|
* Cosine similarity between two vectors
|
|
*/
|
|
private cosineSimilarity;
|
|
/**
|
|
* Index a topic for similarity search
|
|
*/
|
|
indexTopic(topic: string): void;
|
|
/**
|
|
* Add message to batch queue for efficient Gun writes
|
|
*/
|
|
queueMessage(topic: string, payload: any): void;
|
|
/**
|
|
* Get batched messages for efficient Gun write
|
|
*/
|
|
flushBatch(): Array<{
|
|
topic: string;
|
|
payload: any;
|
|
}>;
|
|
/**
|
|
* Start periodic batch flushing
|
|
*/
|
|
private startBatchFlusher;
|
|
/**
|
|
* Get optimization metrics
|
|
*/
|
|
getMetrics(): {
|
|
indexedTopics: number;
|
|
batchQueueSize: number;
|
|
totalMessages: number;
|
|
};
|
|
/**
|
|
* Cleanup resources
|
|
*/
|
|
destroy(): void;
|
|
}
|
|
/**
|
|
* Create and connect a P2P swarm
|
|
*/
|
|
export declare function createP2PSwarmV2(agentId: string, swarmKey?: string): Promise<P2PSwarmV2>;
|
|
export { stableStringify, IdentityManager, CryptoV2, RelayManager, ArtifactStore, RuVectorGunOptimizer, };
|
|
export type { SignedEnvelope, TaskEnvelope, TaskReceipt, ArtifactPointer, };
|
|
//# sourceMappingURL=p2p-swarm-v2.d.ts.map
|