@kya-os/mcp-i
Version:
The TypeScript MCP framework with identity features built-in
221 lines (220 loc) • 7.91 kB
JavaScript
;
/**
* 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);
}