UNPKG

@kya-os/mcp-i

Version:

The TypeScript MCP framework with identity features built-in

221 lines (220 loc) 7.91 kB
"use strict"; /** * Delegation Credential Issuer * * Issues W3C Verifiable Credentials for delegations with Ed25519 signatures. * Follows the Python POC design (Delegation-Service.md:136-163) where * delegations are issued AS W3C VCs. * * Related Spec: MCP-I §4.1, §4.2, W3C VC Data Model 1.1 * Python Reference: Delegation-Service.md */ Object.defineProperty(exports, "__esModule", { value: true }); exports.DelegationCredentialIssuer = void 0; exports.createDelegationIssuer = createDelegationIssuer; const jose_1 = require("jose"); const json_canonicalize_1 = require("json-canonicalize"); const crypto_1 = require("crypto"); const delegation_1 = require("@kya-os/contracts/delegation"); /** * Delegation Credential Issuer * * Issues W3C Verifiable Credentials for delegations. * Per Python POC (Delegation-Service.md:136-146): * - Every delegation MUST be issued as a VC * - VC is signed with Ed25519 (Ed25519Signature2020) * - StatusList2021 support for efficient revocation */ class DelegationCredentialIssuer { identity; constructor(identity) { this.identity = identity; } /** * Issue a delegation credential * * Creates a W3C Verifiable Credential from a delegation record. * Signs it with Ed25519 and returns the complete DelegationCredential. * * @param delegation - The delegation record to issue as a VC * @param options - Issuance options * @returns Signed DelegationCredential */ async issueDelegationCredential(delegation, options = {}) { // Step 1: Create unsigned VC let unsignedVC = (0, delegation_1.wrapDelegationAsVC)(delegation, { id: options.id, issuanceDate: options.issuanceDate, expirationDate: options.expirationDate, credentialStatus: options.credentialStatus, }); // Add additional contexts if provided if (options.additionalContexts && options.additionalContexts.length > 0) { const existingContexts = unsignedVC['@context']; unsignedVC = { ...unsignedVC, '@context': [...existingContexts, ...options.additionalContexts], }; } // Step 2: Canonicalize VC (for signing) const canonicalVC = this.canonicalizeVC(unsignedVC); // Step 3: Sign with Ed25519 const proof = await this.signVC(unsignedVC, canonicalVC); // Step 4: Return signed VC return { ...unsignedVC, proof, }; } /** * Create a delegation record and issue it as a VC in one step * * Convenience method for creating a new delegation from scratch. * * @param params - Delegation parameters * @param options - Issuance options * @returns Signed DelegationCredential */ async createAndIssueDelegation(params, options = {}) { const now = Date.now(); // Create delegation record const delegation = { id: params.id, issuerDid: params.issuerDid, subjectDid: params.subjectDid, controller: params.controller, vcId: options.id || `urn:uuid:${params.id}`, parentId: params.parentId, constraints: params.constraints, signature: '', // Will be filled by VC proof status: params.status || 'active', createdAt: now, metadata: params.metadata, }; // Issue as VC return this.issueDelegationCredential(delegation, options); } /** * Canonicalize VC for signing * * Uses JCS (JSON Canonicalization Scheme, RFC 8785) to create * a deterministic representation of the VC. * * @param vc - The unsigned VC * @returns Canonical JSON string */ canonicalizeVC(vc) { // Per W3C VC spec, we canonicalize the VC without the proof return (0, json_canonicalize_1.canonicalize)(vc); } /** * Sign VC with Ed25519 (Ed25519Signature2020) * * Creates an Ed25519Signature2020 proof for the VC. * Uses the same signing pattern as proof generation. * * @param vc - The unsigned VC * @param canonicalVC - The canonical representation for signing * @returns Proof object */ async signVC(vc, canonicalVC) { try { // Import private key const privateKeyPem = this.formatPrivateKeyAsPEM(this.identity.privateKey); const privateKey = await (0, jose_1.importPKCS8)(privateKeyPem, 'EdDSA'); // Create verification method URI const verificationMethod = `${this.identity.did}#${this.identity.kid}`; // Create signing data (hash of canonical VC) const dataToSign = (0, crypto_1.createHash)('sha256') .update(canonicalVC, 'utf8') .digest(); // Sign using jose (creates a detached JWS) // We'll create a compact JWS and extract the signature const jwt = await new jose_1.SignJWT({ digest: dataToSign.toString('base64') }) .setProtectedHeader({ alg: 'EdDSA', typ: 'VC', }) .setIssuedAt() .sign(privateKey); // Extract signature from JWT (third part of compact JWS) const parts = jwt.split('.'); const signatureBase64url = parts[2]; // Create Ed25519Signature2020 proof const proof = { type: 'Ed25519Signature2020', created: new Date().toISOString(), verificationMethod, proofPurpose: 'assertionMethod', proofValue: signatureBase64url, }; return proof; } catch (error) { throw new Error(`Failed to sign delegation credential: ${error instanceof Error ? error.message : 'Unknown error'}`); } } /** * Format base64 private key as PKCS#8 PEM for JOSE library * * Same as proof generator format. */ formatPrivateKeyAsPEM(base64PrivateKey) { const keyData = Buffer.from(base64PrivateKey, 'base64'); // Ed25519 PKCS#8 header and footer const header = '-----BEGIN PRIVATE KEY-----\n'; const footer = '\n-----END PRIVATE KEY-----'; // Wrap Ed25519 raw key in PKCS#8 structure (ASN.1 encoding) const pkcs8Header = Buffer.from([ 0x30, 0x2e, // SEQUENCE, length 46 0x02, 0x01, 0x00, // INTEGER version 0 0x30, 0x05, // SEQUENCE, length 5 0x06, 0x03, 0x2b, 0x65, 0x70, // OID for Ed25519 0x04, 0x22, // OCTET STRING, length 34 0x04, 0x20, // OCTET STRING, length 32 (the actual key) ]); const fullKey = Buffer.concat([pkcs8Header, keyData.subarray(0, 32)]); const base64Key = fullKey.toString('base64'); // Format as PEM with line breaks every 64 characters const formattedKey = base64Key.match(/.{1,64}/g)?.join('\n') || base64Key; return header + formattedKey + footer; } /** * Get issuer DID * * @returns The DID of this issuer */ getIssuerDid() { return this.identity.did; } /** * Get issuer key ID * * @returns The key ID of this issuer */ getIssuerKeyId() { return this.identity.kid; } } exports.DelegationCredentialIssuer = DelegationCredentialIssuer; /** * Create a delegation credential issuer from identity * * Convenience factory function. * * @param identity - Agent identity * @returns DelegationCredentialIssuer instance */ function createDelegationIssuer(identity) { return new DelegationCredentialIssuer(identity); }