@kya-os/mcp-i 1.7.8 → 1.7.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -245,39 +245,73 @@ class DelegationCredentialVerifier {
245
245
  const vcWithoutProof = { ...vc };
246
246
  delete vcWithoutProof.proof;
247
247
  // The proof.proofValue is base64url-encoded Ed25519 signature
248
- // The proof.jws is an alternative JWS-based signature format
249
- const proofValue = vc.proof?.proofValue || vc.proof?.jws;
250
- if (!proofValue) {
248
+ // The proof.jws is an alternative JWS-based signature format (header.payload.signature)
249
+ const rawProofValue = vc.proof?.proofValue || vc.proof?.jws;
250
+ if (!rawProofValue) {
251
251
  return {
252
252
  valid: false,
253
253
  reason: 'Proof missing proofValue or jws',
254
254
  durationMs: Date.now() - startTime,
255
255
  };
256
256
  }
257
- try {
258
- // For Ed25519Signature2020, we need to:
259
- // 1. Canonicalize the VC without proofValue
260
- // 2. Canonicalize the proof options
261
- // 3. Hash both and concatenate
262
- // 4. Verify the signature
257
+ // Extract signature bytes and construct signing input
258
+ // Handle both JWS format (header.payload.signature) and proofValue format
259
+ let signatureBase64url;
260
+ let signingInput;
261
+ if (rawProofValue.includes('.')) {
262
+ // JWS format with b64:false (standard for Ed25519Signature2020)
263
+ // The signing input is: base64url(header) + "." + payload_bytes
264
+ const jwsParts = rawProofValue.split('.');
265
+ if (jwsParts.length !== 3) {
266
+ return {
267
+ valid: false,
268
+ reason: `Invalid JWS format: expected 3 parts, got ${jwsParts.length}`,
269
+ durationMs: Date.now() - startTime,
270
+ };
271
+ }
272
+ const [headerB64, payloadB64, signatureB64] = jwsParts;
273
+ signatureBase64url = signatureB64;
274
+ // For b64:false JWS, the signing input is the detached payload format:
275
+ // ASCII(BASE64URL(UTF8(JWS Protected Header))) || '.' || JWS Payload
276
+ // The payload is the hash of the document (for Ed25519Signature2020)
277
+ // Per RFC 7797 (JWS Unencoded Payload Option)
278
+ // Construct proof options for hashing (everything in proof except signature fields)
279
+ const proofOptions = { ...vc.proof };
280
+ delete proofOptions.proofValue;
281
+ delete proofOptions.jws;
282
+ delete proofOptions.signatureValue;
283
+ const canonicalDoc = (0, json_canonicalize_1.canonicalize)(vcWithoutProof);
284
+ const canonicalProof = (0, json_canonicalize_1.canonicalize)(proofOptions);
285
+ // Hash both and concatenate (payload for JWS)
286
+ const docHash = (0, crypto_1.createHash)('sha256').update(canonicalDoc, 'utf8').digest();
287
+ const proofHash = (0, crypto_1.createHash)('sha256').update(canonicalProof, 'utf8').digest();
288
+ const payloadBytes = Buffer.concat([proofHash, docHash]);
289
+ // JWS signing input: header + "." + payload_bytes
290
+ const headerBytes = Buffer.from(headerB64, 'utf8');
291
+ const separator = Buffer.from('.', 'utf8');
292
+ signingInput = Buffer.concat([headerBytes, separator, payloadBytes]);
293
+ }
294
+ else {
295
+ // proofValue is raw base64url signature (Ed25519Signature2020 native format)
296
+ signatureBase64url = rawProofValue;
263
297
  // Construct proof options (everything in proof except proofValue)
264
298
  const proofOptions = { ...vc.proof };
265
299
  delete proofOptions.proofValue;
266
300
  delete proofOptions.jws;
267
301
  delete proofOptions.signatureValue;
268
- // Create signing input: hash(canonicalize(document)) + hash(canonicalize(proofOptions))
302
+ // Create signing input: hash(canonicalize(proofOptions)) || hash(canonicalize(document))
269
303
  const canonicalDoc = (0, json_canonicalize_1.canonicalize)(vcWithoutProof);
270
304
  const canonicalProof = (0, json_canonicalize_1.canonicalize)(proofOptions);
271
- // For Ed25519Signature2020, signing input is:
272
- // SHA-256(canonicalize(proofOptions)) || SHA-256(canonicalize(document))
273
305
  const docHash = (0, crypto_1.createHash)('sha256').update(canonicalDoc, 'utf8').digest();
274
306
  const proofHash = (0, crypto_1.createHash)('sha256').update(canonicalProof, 'utf8').digest();
275
- const signingInput = Buffer.concat([proofHash, docHash]);
307
+ signingInput = Buffer.concat([proofHash, docHash]);
308
+ }
309
+ try {
276
310
  // Decode the base64url signature
277
- const signatureBytes = Buffer.from(proofValue, 'base64url');
311
+ const signatureBytes = Buffer.from(signatureBase64url, 'base64url');
278
312
  // Use subtle crypto for Ed25519 verification
279
313
  const cryptoKey = await globalThis.crypto.subtle.importKey('jwk', publicKeyJwk, { name: 'Ed25519' }, false, ['verify']);
280
- const isValid = await globalThis.crypto.subtle.verify({ name: 'Ed25519' }, cryptoKey, signatureBytes, signingInput);
314
+ const isValid = await globalThis.crypto.subtle.verify({ name: 'Ed25519' }, cryptoKey, new Uint8Array(signatureBytes), new Uint8Array(signingInput));
281
315
  if (!isValid) {
282
316
  return {
283
317
  valid: false,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kya-os/mcp-i",
3
- "version": "1.7.8",
3
+ "version": "1.7.10",
4
4
  "description": "The TypeScript MCP framework with identity features built-in",
5
5
  "type": "commonjs",
6
6
  "main": "dist/index.js",