UNPKG

anon-identity

Version:

Decentralized identity framework with DIDs, Verifiable Credentials, and privacy-preserving selective disclosure

301 lines 11.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.MessageProtocol = void 0; const types_1 = require("./types"); const crypto_1 = require("../../core/crypto"); const did_1 = require("../../core/did"); class MessageProtocol { /** * Creates a new message with proper formatting and metadata */ static createMessage(type, from, to, payload, options) { const now = new Date(); const expiresAt = options?.expiresAt || new Date(now.getTime() + this.MESSAGE_TTL); return { id: this.generateMessageId(), type, from, to, timestamp: now, version: this.VERSION, replyTo: options?.replyTo, expiresAt, metadata: options?.metadata, payload }; } /** * Signs a message with the sender's private key */ static async signMessage(message, senderKeyPair) { // Create canonical representation for signing const { signature, ...messageWithoutSignature } = message; const canonical = this.canonicalizeMessage(messageWithoutSignature); // Sign the message const messageSignature = (0, crypto_1.signData)(canonical, senderKeyPair.privateKey); return { ...message, signature: messageSignature }; } /** * Verifies a message signature */ static async verifyMessage(message) { if (!message.signature) { return false; } try { // Extract sender's public key from DID const senderPublicKey = did_1.DIDService.getPublicKeyFromDID(message.from); // Create canonical representation const { signature, ...messageWithoutSignature } = message; const canonical = this.canonicalizeMessage(messageWithoutSignature); // Verify signature return (0, crypto_1.verifyData)(canonical, message.signature, senderPublicKey); } catch (error) { return false; } } /** * Validates message structure and content */ static validateMessage(message) { const errors = []; const warnings = []; // Check required fields if (!message.id) errors.push('Message ID is required'); if (!message.type) errors.push('Message type is required'); if (!message.from) errors.push('Sender DID is required'); if (!message.to) errors.push('Recipient DID is required'); if (!message.timestamp) errors.push('Timestamp is required'); if (!message.version) errors.push('Version is required'); // Validate DID format if (message.from && !this.isValidDID(message.from)) { errors.push('Invalid sender DID format'); } if (message.to && !this.isValidDID(message.to)) { errors.push('Invalid recipient DID format'); } // Check message expiration if (message.expiresAt && new Date() > message.expiresAt) { errors.push('Message has expired'); } // Check version compatibility if (message.version !== this.VERSION) { warnings.push(`Version mismatch: expected ${this.VERSION}, got ${message.version}`); } // Validate message type if (!Object.values(types_1.AgentMessageType).includes(message.type)) { errors.push(`Unknown message type: ${message.type}`); } // Type-specific validation const typeValidation = this.validateMessagePayload(message); errors.push(...typeValidation.errors); warnings.push(...typeValidation.warnings); return { valid: errors.length === 0, errors: errors.length > 0 ? errors : undefined, warnings: warnings.length > 0 ? warnings : undefined }; } /** * Creates a message envelope for transport */ static createEnvelope(message, options) { const envelope = { message }; if (options?.routingPath || options?.ttl) { envelope.routingInfo = { path: options.routingPath || [], ttl: options.ttl || 10 }; } if (options?.encryption) { envelope.encryption = { algorithm: options.encryption.algorithm, recipientKey: options.encryption.recipientKey }; } return envelope; } /** * Extracts message from envelope and validates */ static async processEnvelope(envelope, agentKeyPair) { const errors = []; // Decrypt if necessary let message = envelope.message; if (envelope.encryption && envelope.encryption.encryptedContent) { if (!agentKeyPair) { errors.push('Encrypted message received but no keys provided for decryption'); return { message, errors }; } try { // In a real implementation, decrypt the content here // For now, we'll skip actual decryption message = JSON.parse(envelope.encryption.encryptedContent); } catch (error) { errors.push('Failed to decrypt message'); return { message, errors }; } } // Validate message const validation = this.validateMessage(message); if (!validation.valid) { errors.push(...(validation.errors || [])); } // Verify signature const signatureValid = await this.verifyMessage(message); if (!signatureValid) { errors.push('Invalid message signature'); } return { message, errors: errors.length > 0 ? errors : undefined }; } /** * Creates standard response messages */ static createResponse(originalMessage, responseType, payload, from) { return this.createMessage(responseType, from, originalMessage.from, payload, { replyTo: originalMessage.id, metadata: { originalType: originalMessage.type, responseTo: originalMessage.id } }); } /** * Creates error response */ static createErrorResponse(originalMessage, error, from) { return this.createResponse(originalMessage, types_1.AgentMessageType.ERROR, { error, originalMessageId: originalMessage.id, originalMessageType: originalMessage.type }, from); } /** * Creates acknowledgment response */ static createAckResponse(originalMessage, from, status = 'received') { return this.createResponse(originalMessage, types_1.AgentMessageType.ACK, { status, originalMessageId: originalMessage.id, timestamp: new Date() }, from); } // Private helper methods static generateMessageId() { const timestamp = Date.now(); const random = Math.random().toString(36).substring(2); return `msg_${timestamp}_${random}`; } static canonicalizeMessage(message) { // Create a canonical string representation for signing const keys = Object.keys(message).sort(); const canonical = {}; keys.forEach(key => { const value = message[key]; if (value !== undefined) { canonical[key] = value; } }); return JSON.stringify(canonical); } static isValidDID(did) { return did.startsWith('did:') && did.includes(':') && did.length > 10; } static validateMessagePayload(message) { const errors = []; const warnings = []; if (!message.payload) { errors.push('Message payload is required'); return { errors, warnings }; } switch (message.type) { case types_1.AgentMessageType.DELEGATION_REQUEST: if (!message.payload.requestedScopes || !Array.isArray(message.payload.requestedScopes)) { errors.push('Delegation request must include requestedScopes array'); } if (message.payload.requestedScopes?.length === 0) { warnings.push('Delegation request has empty scopes array'); } break; case types_1.AgentMessageType.DELEGATION_GRANT: if (!message.payload.credential) { errors.push('Delegation grant must include credential'); } if (!message.payload.grantedScopes || !Array.isArray(message.payload.grantedScopes)) { errors.push('Delegation grant must include grantedScopes array'); } break; case types_1.AgentMessageType.DELEGATION_DENY: if (!message.payload.reason) { errors.push('Delegation deny must include reason'); } break; case types_1.AgentMessageType.QUERY_STATUS: // Status queries can have empty payloads break; case types_1.AgentMessageType.RESPONSE_STATUS: if (!message.payload.status) { errors.push('Status response must include status'); } if (typeof message.payload.delegationDepth !== 'number') { errors.push('Status response must include delegationDepth number'); } break; case types_1.AgentMessageType.PING: // Ping can have empty payload break; case types_1.AgentMessageType.PONG: // Pong can have empty payload break; case types_1.AgentMessageType.ERROR: if (!message.payload.error) { errors.push('Error message must include error description'); } break; default: warnings.push(`Unknown message type validation: ${message.type}`); } return { errors, warnings }; } /** * Serializes a message for transport */ static serialize(message) { return JSON.stringify(message, (key, value) => { // Handle Date objects if (value instanceof Date) { return value.toISOString(); } return value; }); } /** * Deserializes a message from transport */ static deserialize(data) { return JSON.parse(data, (key, value) => { // Handle Date objects if (typeof value === 'string' && /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/.test(value)) { return new Date(value); } return value; }); } } exports.MessageProtocol = MessageProtocol; MessageProtocol.VERSION = '1.0.0'; MessageProtocol.MESSAGE_TTL = 5 * 60 * 1000; // 5 minutes //# sourceMappingURL=message-protocol.js.map