UNPKG

atp-sdk

Version:

Official TypeScript SDK for Agent Trust Protocol™ - Build secure, verifiable, and trustworthy applications with decentralized identity, verifiable credentials, payment protocols (AP2/ACP), and robust access control

809 lines 28.9 kB
/** * Simplified Agent API for ATP™ * * Provides a 3-line quick start experience for developers * while maintaining full quantum-safe security features. */ import { EventEmitter } from 'events'; import { ATPClient } from './client/atp.js'; import { CryptoUtils } from './utils/crypto.js'; import { TrustScoring, TrustLevel } from './utils/trust.js'; import { BehaviorMerkleTree, createChallenge, createBehaviorCommitment, createBehaviorProof, verifyBehaviorProof, generateAuthResponse, verifyAuthResponse, isChallengeExpired } from './utils/zkp.js'; export class Agent extends EventEmitter { constructor(name, options) { super(); this.did = null; this.privateKey = null; this.initialized = false; this._quantumSafe = false; this.eventHandlers = new Map(); // X25519 key pair for message encryption (separate from signing keys) this.encryptionPublicKey = null; this.encryptionPrivateKey = null; // Trust scoring engine this.trustScoring = new TrustScoring(); // Behavior tracking for ZKP proofs (ATP unique differentiator) this.behaviorTree = new BehaviorMerkleTree(); this.behaviorStats = { successCount: 0, violationCount: 0 }; // Pending ZKP challenges (for async challenge-response flows) this.pendingChallenges = new Map(); // Cached DID document for ZKP identity proofs this.didDocument = null; // Cached credentials for ZKP credential proofs this.cachedCredentials = []; this.name = name; // Default to local services (overridable via options or env). Audit defaults to 3006 to match mocks. const baseUrl = options?.serverUrl || process.env.ATP_SERVER_URL || 'http://localhost'; const identityUrl = process.env.ATP_IDENTITY_URL || `${baseUrl}:3001`; const credentialsUrl = process.env.ATP_CREDENTIALS_URL || `${baseUrl}:3002`; const permissionsUrl = process.env.ATP_PERMISSIONS_URL || `${baseUrl}:3003`; const auditUrl = process.env.ATP_AUDIT_URL || `${baseUrl}:3005`; const gatewayUrl = process.env.ATP_GATEWAY_URL || `${baseUrl}:3000`; const config = { baseUrl, services: { identity: identityUrl, credentials: credentialsUrl, permissions: permissionsUrl, audit: auditUrl, gateway: gatewayUrl } }; this.client = new ATPClient(config); if (options?.did && options?.privateKey) { this.did = options.did; this.privateKey = options.privateKey; } } /** * Create a new agent with quantum-safe identity * * @example * ```typescript * const agent = await Agent.create('MyBot'); * ``` */ static async create(name, options) { const agent = new Agent(name, options); await agent.initialize(); return agent; } async initialize() { if (this.initialized) return; try { // If no DID provided, create a new quantum-safe identity if (!this.did || !this.privateKey) { // Generate hybrid quantum-safe keypair (Ed25519 + ML-DSA) // This provides protection against both classical and quantum attacks const keyPair = await CryptoUtils.generateKeyPair(true); // quantumSafe = true by default const identity = await this.client.identity.registerDID({ publicKey: keyPair.publicKey, metadata: { name: this.name, quantumSafe: keyPair.quantumSafe, algorithm: keyPair.quantumSafe ? 'hybrid-ed25519-mldsa65' : 'ed25519' } }); if (identity.data) { this.did = identity.data.did; this.privateKey = keyPair.privateKey; // Store quantum-safe flag for signing operations this._quantumSafe = keyPair.quantumSafe; } else { throw new Error('Failed to register DID'); } } // Generate X25519 key pair for message encryption // (separate from signing keys for better security practices) const encryptionKeys = CryptoUtils.generateX25519KeyPair(); this.encryptionPublicKey = encryptionKeys.publicKey; this.encryptionPrivateKey = encryptionKeys.privateKey; // Set up authentication this.client.setAuthentication({ did: this.did, privateKey: this.privateKey }); this.initialized = true; } catch (error) { throw new Error(`Failed to initialize agent: ${error instanceof Error ? error.message : 'Unknown error'}`); } } /** * Send a secure message to another agent * * @example * ```typescript * // Unencrypted message (for systems without encryption key exchange) * await agent.send(otherAgentDid, 'Hello, quantum world!'); * * // Encrypted message (recommended) * await agent.send(otherAgentDid, 'Secret message', { * recipientEncryptionKey: otherAgent.getEncryptionPublicKey() * }); * ``` */ async send(recipientDid, message, options) { if (!this.initialized) { throw new Error('Agent not initialized'); } const payload = typeof message === 'string' ? { text: message } : message; const payloadString = JSON.stringify(payload); const shouldEncrypt = options?.encrypt !== false && options?.recipientEncryptionKey; let encryptedPayload; if (shouldEncrypt && options?.recipientEncryptionKey) { // Encrypt the message using the recipient's X25519 public key encryptedPayload = CryptoUtils.encryptForRecipient(payloadString, options.recipientEncryptionKey); } const messageId = CryptoUtils.generateId(); // Log the message send event (for audit trail) await this.client.audit.logEvent({ source: 'agent-sdk', action: 'message.sent', resource: recipientDid, actor: this.did, details: { messageId, from: this.did, to: recipientDid, timestamp: new Date().toISOString(), encrypted: !!encryptedPayload, // Only include preview if not encrypted ...(encryptedPayload ? { encryptedSize: encryptedPayload.length } : { preview: `${payloadString.substring(0, 50)}...` }) } }); return { encrypted: !!encryptedPayload, messageId }; } /** * Decrypt a message received from another agent * * @example * ```typescript * agent.on('message', (event) => { * if (event.encrypted) { * const decrypted = agent.decryptMessage(event.encryptedContent); * console.log('Decrypted:', decrypted); * } * }); * ``` */ decryptMessage(encryptedMessage) { if (!this.initialized || !this.encryptionPrivateKey) { throw new Error('Agent not initialized or missing encryption keys'); } return CryptoUtils.decryptFromSender(encryptedMessage, this.encryptionPrivateKey); } /** * Get the agent's encryption public key (for receiving encrypted messages) * * @example * ```typescript * // Share your encryption public key with other agents * const myEncKey = agent.getEncryptionPublicKey(); * // Other agents can then send encrypted messages to you * ``` */ getEncryptionPublicKey() { if (!this.initialized || !this.encryptionPublicKey) { throw new Error('Agent not initialized'); } return this.encryptionPublicKey; } /** * Get the trust score for another agent (simple number for backward compatibility) * * @example * ```typescript * const trustScore = await agent.getTrustScore(otherAgentDid); * console.log(`Trust level: ${trustScore}`); * ``` */ async getTrustScore(agentDid) { if (!this.initialized) { throw new Error('Agent not initialized'); } try { // Check if we have any interaction history const events = await this.client.audit.queryEvents({ actor: this.did, resource: agentDid, limit: 100 // Get more events for better scoring }); const interactionCount = events.data?.events?.length || 0; // Use backward-compatible scoring for simple getTrustScore return TrustScoring.levelFromCount(interactionCount); } catch { // Default to unknown trust level if query fails return 0; } } /** * Get detailed trust assessment for another agent * * @example * ```typescript * const trust = await agent.assessTrust(otherAgentDid); * console.log(`Trust score: ${trust.score}`); * console.log(`Level: ${trust.level}`); * console.log(`Confidence: ${trust.confidence}`); * console.log(`Factors:`, trust.factors); * ``` */ async assessTrust(agentDid) { if (!this.initialized) { throw new Error('Agent not initialized'); } try { // Get interaction history const eventsResponse = await this.client.audit.queryEvents({ actor: this.did, resource: agentDid, limit: 100 }); // Get credentials for the agent (if available) let verifiedCredentials = 0; try { const credentialsResponse = await this.client.credentials.getCredentialsForDID(agentDid, { status: 'active' }); verifiedCredentials = credentialsResponse.data?.credentials?.length || 0; } catch { // Credentials service may not be available verifiedCredentials = 0; } // Convert audit events to interaction events const interactions = (eventsResponse.data?.events || []).map((event) => ({ timestamp: event.timestamp || new Date().toISOString(), action: event.action || 'unknown', success: event.details?.success !== false // Assume success unless explicitly false })); // Calculate comprehensive trust score return this.trustScoring.calculateTrustScore(interactions, verifiedCredentials); } catch { // Return default unknown score on error return { score: 0, level: TrustLevel.UNKNOWN, factors: { interactionScore: 0, recencyScore: 0, credentialScore: 0, successScore: 0.5 }, confidence: 0, metadata: { totalInteractions: 0, successfulInteractions: 0, credentialsVerified: 0, assessedAt: new Date().toISOString() } }; } } /** * Grant a capability to another agent * * @example * ```typescript * await agent.grantCapability(otherAgentDid, 'read:data'); * ``` */ async grantCapability(agentDid, capability) { if (!this.initialized) { throw new Error('Agent not initialized'); } await this.client.permissions.grantPermission({ subject: agentDid, resource: `${this.did}:*`, action: capability, conditions: {}, expiresAt: new Date(Date.now() + 86400000).toISOString() // 24 hours }); } /** * Issue a verifiable credential to another agent * * @example * ```typescript * await agent.issueCredential(otherAgentDid, 'verified-partner', { level: 'gold' }); * ``` */ async issueCredential(subjectDid, credentialType, claims) { if (!this.initialized) { throw new Error('Agent not initialized'); } const credential = await this.client.credentials.issueCredential({ subjectDID: subjectDid, credentialType, claims, expirationDate: new Date(Date.now() + 365 * 24 * 60 * 60 * 1000).toISOString() // 1 year }); return credential.data?.id || 'credential-id'; } /** * Get the agent's DID (Decentralized Identifier) */ getDID() { if (!this.did) { throw new Error('Agent not initialized'); } return this.did; } /** * Get the agent's name */ getName() { return this.name; } /** * Check if the agent is initialized */ isInitialized() { return this.initialized; } /** * Check if the agent uses quantum-safe cryptography */ isQuantumSafe() { return this._quantumSafe === true || !!(this.privateKey && this.privateKey.length > 8000); // Hybrid keys are ~8000 hex chars (4032 bytes) } /** * Subscribe to agent events * * @example * ```typescript * agent.on('message', (msg) => console.log('Received:', msg)); * agent.on('trust.changed', (event) => console.log(`Trust: ${event.previousLevel} -> ${event.currentLevel}`)); * agent.on('error', (err) => console.error('Agent error:', err)); * ``` */ on(event, handler) { // Register handler in our custom map for type safety if (!this.eventHandlers.has(event)) { this.eventHandlers.set(event, new Set()); } this.eventHandlers.get(event).add(handler); // Also use EventEmitter's on() for actual event dispatching super.on(event, handler); return this; } /** * Subscribe to an event once * * @example * ```typescript * agent.once('connection.open', () => console.log('Connected!')); * ``` */ once(event, handler) { super.once(event, handler); return this; } /** * Remove an event handler * * @example * ```typescript * agent.off('message', messageHandler); * ``` */ off(event, handler) { if (this.eventHandlers.has(event)) { this.eventHandlers.get(event).delete(handler); } super.off(event, handler); return this; } /** * Remove all handlers for an event (or all events if no event specified) */ removeAllListeners(event) { if (event) { this.eventHandlers.delete(event); } else { this.eventHandlers.clear(); } super.removeAllListeners(event); return this; } /** * Emit an event to all registered handlers * @internal Used internally to dispatch events */ emitAgentEvent(event, data) { const eventData = { type: event, timestamp: new Date().toISOString(), ...data }; return this.emit(event, eventData); } /** * Get the number of listeners for an event */ listenerCount(event) { return super.listenerCount(event); } /** * Get all registered event types */ getRegisteredEvents() { return Array.from(this.eventHandlers.keys()); } /** * Check if there are any handlers for an event */ hasListeners(event) { return this.listenerCount(event) > 0; } /** * Establish trust with another agent * * @example * ```typescript * const trust = await agent.establishTrust(otherAgentDid); * if (trust.established) { * console.log('Trust established!'); * } * ``` */ async establishTrust(agentDid, minTrustLevel = 0.5) { if (!this.initialized) { throw new Error('Agent not initialized'); } const currentTrust = await this.getTrustScore(agentDid); if (currentTrust >= minTrustLevel) { return { established: true, level: currentTrust }; } // In production, this would initiate a trust establishment protocol // For now, we'll just log the attempt await this.client.audit.logEvent({ source: 'agent-sdk', action: 'trust.establish.attempted', resource: agentDid, actor: this.did, details: { from: this.did, to: agentDid, currentLevel: currentTrust, requiredLevel: minTrustLevel } }); return { established: false, level: currentTrust }; } // =========================================================================== // ZKP-Based Agent-to-Agent Authentication (ATP Differentiator) // =========================================================================== /** * Record an interaction outcome for behavior tracking * Called automatically after interactions, but can be called manually * * @example * ```typescript * // Record successful interaction * agent.recordInteraction('interaction-123', 'success'); * * // Record violation (e.g., rate limit exceeded) * agent.recordInteraction('interaction-456', 'violation'); * ``` */ recordInteraction(interactionId, outcome) { const commitment = createBehaviorCommitment(interactionId, outcome); this.behaviorTree.addCommitment(commitment); if (outcome === 'success') { this.behaviorStats.successCount++; } else { this.behaviorStats.violationCount++; } // Emit event for monitoring this.emitAgentEvent('trust.changed', { data: { interactionId, outcome, totalSuccesses: this.behaviorStats.successCount, totalViolations: this.behaviorStats.violationCount } }); } /** * Get behavior statistics */ getBehaviorStats() { return { ...this.behaviorStats, merkleRoot: this.behaviorTree.getRoot() }; } /** * Request authentication from another agent using ZKP * Returns a challenge that the other agent must respond to * * @example * ```typescript * // Request proof that another agent has trust >= 0.5 and no violations * const challenge = await agent.requestAuth(otherAgentDid, [ * { type: ZKProofType.TRUST_LEVEL, params: { minTrustLevel: 0.5 } }, * { type: ZKProofType.BEHAVIOR, params: { behaviorType: 'no_violations' } } * ]); * ``` */ async requestAuth(targetDid, requirements) { if (!this.initialized || !this.did) { throw new Error('Agent not initialized'); } const challenge = createChallenge(this.did, targetDid, requirements); // Store challenge for later verification this.pendingChallenges.set(challenge.id, challenge); // Log the auth request await this.client.audit.logEvent({ source: 'agent-sdk', action: 'zkp.auth.requested', resource: targetDid, actor: this.did, details: { challengeId: challenge.id, proofTypes: requirements.map(r => r.type), expiresAt: challenge.expiresAt } }); return challenge; } /** * Respond to an authentication challenge from another agent * Generates ZKP proofs without revealing sensitive data * * @example * ```typescript * // Respond to a challenge received from another agent * const response = await agent.respondToChallenge(challenge); * // Send response back to the verifier * ``` */ async respondToChallenge(challenge) { if (!this.initialized || !this.did || !this.privateKey) { throw new Error('Agent not initialized'); } if (isChallengeExpired(challenge)) { throw new Error('Challenge has expired'); } // Ensure we have a DID document for identity proofs let didDoc; if (this.didDocument) { didDoc = this.didDocument; } else { try { const resolved = await this.client.identity.resolveDID(this.did); didDoc = resolved.data?.document || this.createDefaultDIDDocument(); this.didDocument = didDoc; } catch { didDoc = this.createDefaultDIDDocument(); this.didDocument = didDoc; } } // Get current trust score for trust level proofs const trustResult = await this.assessTrust(this.did); // Generate auth response with all required proofs const response = await generateAuthResponse(challenge, { did: this.did, didDocument: didDoc, trustScore: trustResult.score, credentials: this.cachedCredentials, behaviorTree: this.behaviorTree, behaviorStats: this.behaviorStats }, this.privateKey); // Log the response await this.client.audit.logEvent({ source: 'agent-sdk', action: 'zkp.auth.responded', resource: challenge.verifierDid, actor: this.did, details: { challengeId: challenge.id, proofsGenerated: response.proofs.length } }); return response; } /** * Verify another agent's ZKP authentication response * * @example * ```typescript * // Verify the response from another agent * const result = await agent.verifyAuthResponse(response, challenge); * if (result.verified) { * console.log('Agent authenticated successfully!'); * console.log('Session token:', result.sessionToken); * } * ``` */ async verifyAuthResponse(response, challengeId) { if (!this.initialized || !this.did) { throw new Error('Agent not initialized'); } // Get the challenge (either from pending or by ID) const challenge = challengeId ? this.pendingChallenges.get(challengeId) : this.pendingChallenges.get(response.challengeId); if (!challenge) { return { verified: false, proverDid: response.proverDid, verifiedProofs: [], trustEstablished: false }; } // Verify the response const result = verifyAuthResponse(response, challenge); // Clean up pending challenge this.pendingChallenges.delete(challenge.id); // Log the verification result await this.client.audit.logEvent({ source: 'agent-sdk', action: result.verified ? 'zkp.auth.verified' : 'zkp.auth.failed', resource: response.proverDid, actor: this.did, details: { challengeId: challenge.id, verified: result.verified, trustEstablished: result.trustEstablished, verifiedProofs: result.verifiedProofs.map(p => ({ type: p.type, verified: p.verified })) } }); // If trust was established, record a successful interaction if (result.trustEstablished) { this.recordInteraction(`auth-${challenge.id}`, 'success'); } return result; } /** * One-shot mutual authentication between two agents * Both agents prove to each other simultaneously * * @example * ```typescript * // Mutual authentication with another agent * const result = await agentA.mutualAuth( * agentB.getDID(), * // What I require from them * [{ type: ZKProofType.TRUST_LEVEL, params: { minTrustLevel: 0.5 } }], * // What they require from me * [{ type: ZKProofType.IDENTITY, params: {} }] * ); * * if (result.iVerified.verified && result.theyVerified.verified) { * console.log('Mutual trust established!'); * } * ``` */ async mutualAuth(targetDid, myRequirements, theirRequirements) { if (!this.initialized || !this.did) { throw new Error('Agent not initialized'); } // Create challenge for the other agent (stored for later verification) await this.requestAuth(targetDid, myRequirements); // Create our response to their requirements (simulated challenge from them) const challengeFromThem = createChallenge(targetDid, this.did, theirRequirements); const myResponse = await this.respondToChallenge(challengeFromThem); // In a real implementation, these would be exchanged over the network // For now, we return placeholders for the mutual verification results return { iVerified: { verified: false, // Will be verified when they respond proverDid: targetDid, verifiedProofs: [], trustEstablished: false }, theyVerified: { // This would be their verification of our response verified: true, // Optimistic for now proverDid: this.did, verifiedProofs: myResponse.proofs.map(p => ({ type: p.type, verified: true, timestamp: p.timestamp })), trustEstablished: true } }; } /** * Generate a behavior proof to demonstrate compliance * Proves behavioral integrity without revealing interaction history * * @example * ```typescript * // Prove no violations in the last 90 days * const proof = await agent.proveBehavior({ * type: 'no_violations', * timeRange: { * start: new Date(Date.now() - 90 * 24 * 60 * 60 * 1000).toISOString(), * end: new Date().toISOString() * } * }); * * // Prove 99% success rate * const successProof = await agent.proveBehavior({ * type: 'success_rate', * threshold: 99 * }); * ``` */ async proveBehavior(request) { if (!this.initialized || !this.privateKey) { throw new Error('Agent not initialized'); } return createBehaviorProof(this.behaviorTree, request, this.privateKey, this.behaviorStats); } /** * Verify another agent's behavior proof * * @example * ```typescript * // Verify an agent's no_violations proof * const isValid = await agent.verifyBehaviorProof(proof, { type: 'no_violations' }); * if (isValid) { * console.log('Agent has clean behavior record!'); * } * ``` */ async verifyBehaviorProof(proof, request) { return verifyBehaviorProof(proof, request); } /** * Add a credential to the agent's cache (for ZKP credential proofs) * * @example * ```typescript * // Cache a credential for use in ZKP proofs * agent.addCredential(myVerifiableCredential); * ``` */ addCredential(credential) { this.cachedCredentials.push(credential); } /** * Get cached credentials */ getCredentials() { return [...this.cachedCredentials]; } /** * Create a default DID document for ZKP identity proofs */ createDefaultDIDDocument() { return { id: this.did, '@context': ['https://www.w3.org/ns/did/v1'], verificationMethod: [ { id: `${this.did}#key-1`, type: 'Ed25519VerificationKey2020', controller: this.did, publicKeyMultibase: this.privateKey?.slice(0, 64) // Simplified } ], authentication: [`${this.did}#key-1`] }; } } // Re-export the Agent class as default for cleaner imports export default Agent; //# sourceMappingURL=simple-agent.js.map