Files
mcp/tests/crypto.test.ts
781dbd002f feat: absorb @zap-proto/zap v1.0.1 implementation as @zap-proto/mcp v1.0.0 (#1)
* feat: ZAP JavaScript/TypeScript bindings v0.2.1

JavaScript/TypeScript client library for ZAP (Zero-Copy App Proto).
- Full ZAP protocol support (tools, resources, prompts)
- MCP Gateway bridging
- Post-quantum cryptography (ML-KEM, ML-DSA)
- W3C DID support
- Lux consensus integration

* feat: add documentation site using @hanzo/docs framework

- Next.js 14 docs site in docs/ directory
- Package.json with @hanzo/ui and @hanzo/docs dependencies
- GitHub Actions workflow for deployment to GitHub Pages
- Documentation pages covering:
  - Installation and Quick Start
  - Client API, Gateway, Server
  - TypeScript Types and Configuration
  - Error Handling and Identity/DIDs
  - Browser and Node.js usage guides
  - Code examples and patterns
- Configured basePath '/zap-js' for GitHub Pages
- Uses pnpm and Node 22

* fix: docs build - remove unused @hanzo/docs, upgrade to Next.js 15

- Remove @hanzo/docs and @hanzo/ui dependencies (not actually used)
- Upgrade to Next.js 15 and React 19 for next.config.ts support
- Rename postcss.config.js to postcss.config.cjs for ESM compatibility
- Add pnpm-lock.yaml for reproducible builds

* rebrand: Cap'n Proto → ZAP

Update all documentation to use ZAP branding instead of Cap'n Proto.

* chore: update to Node 22 and latest packages

- Node.js 22 in CI workflow
- fumadocs-core/ui v15
- next v15.1
- react v19
- Consistent workflow across all repos

* fix: revert to fumadocs v14 for compatibility

* fix: update metadata description to use ZAP branding

* chore: rename package to @zap-proto/zap

- Change npm package name from @hanzo/zap to @zap-proto/zap
- Update repository URL to zap-protocol org
- Update homepage to zap-protocol.github.io
- Update keywords and author

* feat: add transport layer and gateway improvements

- Add server transport abstraction
- Add crypto, gateway, and protocol tests
- Update client/server implementations
- Add CI workflow configuration
- Update vitest config with coverage thresholds

* feat: add .zap test schema for native ZAP format testing

* feat: add ZAP-format schema

Convert zap.capnp to proper whitespace-based .zap syntax

* chore: brand-neutral cleanup — remove cross-tenant references

* docs(readme): add canonical link → zap-proto.dev/docs

* docs(readme): also link Native ZAP RPC guide

* test(pqrns): import canonical KAT fixture

* test(pqrns): DID KAT — must match Go reference at zap-proto/rns

* chore: depend on @zap-proto/zap ^1.1.0 (foundation rolled back from premature v2.0.0)

* chore: pin @zap-proto/zap to ^0.1.0 (foundation reset to pre-1.0)

The @zap-proto/zap foundation was reset to v0.1.0 — native ZAP wire
runtime, no Cap'n Proto. Match the pre-1.0 line; no tags until 1.0
works end-to-end across @zap-proto/{zap,web,mcp}.

* chore: depend on @zap-proto/zap ^1.1.0

@zap-proto/zap is published at 1.1.0 (native ZAP wire runtime, back in
the 1.x line). Point the runtime dep at it instead of the pre-publish
^0.1.0. Lockfiles (npm + pnpm) regenerated; 105 tests still pass.

---------

Co-authored-by: Hanzo Dev <[email protected]>
Co-authored-by: z <[email protected]>
2026-06-15 19:46:01 -07:00

140 lines
4.5 KiB
TypeScript

import { describe, it, expect } from 'vitest';
import {
MLKEM_PUBLIC_KEY_SIZE,
MLKEM_CIPHERTEXT_SIZE,
MLKEM_SHARED_SECRET_SIZE,
MLDSA_PUBLIC_KEY_SIZE,
MLDSA_SIGNATURE_SIZE,
X25519_PUBLIC_KEY_SIZE,
HYBRID_SHARED_SECRET_SIZE,
CryptoError,
isWebCryptoAvailable,
isPQAvailable,
PQKeyExchange,
PQSignature,
} from '../src/crypto.js';
describe('Constants', () => {
it('should have correct ML-KEM sizes', () => {
expect(MLKEM_PUBLIC_KEY_SIZE).toBe(1184);
expect(MLKEM_CIPHERTEXT_SIZE).toBe(1088);
expect(MLKEM_SHARED_SECRET_SIZE).toBe(32);
});
it('should have correct ML-DSA sizes', () => {
expect(MLDSA_PUBLIC_KEY_SIZE).toBe(1952);
expect(MLDSA_SIGNATURE_SIZE).toBe(3293);
});
it('should have correct X25519 and hybrid sizes', () => {
expect(X25519_PUBLIC_KEY_SIZE).toBe(32);
expect(HYBRID_SHARED_SECRET_SIZE).toBe(32);
});
});
describe('CryptoError', () => {
it('should create a CryptoError', () => {
const error = new CryptoError('test error');
expect(error.message).toBe('test error');
expect(error.name).toBe('CryptoError');
});
it('should be instanceof Error', () => {
const error = new CryptoError('test');
expect(error).toBeInstanceOf(Error);
});
});
describe('isWebCryptoAvailable', () => {
it('should return boolean', () => {
const result = isWebCryptoAvailable();
expect(typeof result).toBe('boolean');
});
it('should be true in Node.js 18+', () => {
// Node.js 18+ has Web Crypto API
expect(isWebCryptoAvailable()).toBe(true);
});
});
describe('isPQAvailable', () => {
it('should return boolean', () => {
const result = isPQAvailable();
expect(typeof result).toBe('boolean');
});
it('should return false (PQ crypto not implemented)', () => {
// PQ crypto requires liboqs-node which is not installed
expect(isPQAvailable()).toBe(false);
});
});
describe('PQKeyExchange', () => {
it('should throw when generating without PQ support', async () => {
await expect(PQKeyExchange.generate()).rejects.toThrow('PQ crypto not available');
});
it('should create from public key', () => {
const publicKey = new Uint8Array(MLKEM_PUBLIC_KEY_SIZE);
const kex = PQKeyExchange.fromPublicKey(publicKey);
expect(kex.publicKey).toEqual(publicKey);
});
it('should throw on invalid public key size', () => {
const shortKey = new Uint8Array(16);
expect(() => PQKeyExchange.fromPublicKey(shortKey)).toThrow('Invalid ML-KEM public key size');
});
it('should throw when encapsulating without PQ support', async () => {
const publicKey = new Uint8Array(MLKEM_PUBLIC_KEY_SIZE);
const kex = PQKeyExchange.fromPublicKey(publicKey);
const recipientPk = new Uint8Array(MLKEM_PUBLIC_KEY_SIZE);
await expect(kex.encapsulate(recipientPk)).rejects.toThrow('PQ crypto not available');
});
it('should throw when decapsulating without PQ support', async () => {
const publicKey = new Uint8Array(MLKEM_PUBLIC_KEY_SIZE);
const kex = PQKeyExchange.fromPublicKey(publicKey);
const ciphertext = new Uint8Array(MLKEM_CIPHERTEXT_SIZE);
// Will throw because no secret key available (fromPublicKey creates verify-only)
await expect(kex.decapsulate(ciphertext)).rejects.toThrow('PQ crypto not available');
});
});
describe('PQSignature', () => {
it('should throw when generating without PQ support', async () => {
await expect(PQSignature.generate()).rejects.toThrow('PQ crypto not available');
});
it('should create from public key', () => {
const publicKey = new Uint8Array(MLDSA_PUBLIC_KEY_SIZE);
const sig = PQSignature.fromPublicKey(publicKey);
expect(sig.publicKey).toEqual(publicKey);
});
it('should throw on invalid public key size', () => {
const shortKey = new Uint8Array(16);
expect(() => PQSignature.fromPublicKey(shortKey)).toThrow('Invalid ML-DSA public key size');
});
it('should throw when verifying without PQ support', async () => {
const publicKey = new Uint8Array(MLDSA_PUBLIC_KEY_SIZE);
const sig = PQSignature.fromPublicKey(publicKey);
const message = new Uint8Array([1, 2, 3]);
const signature = new Uint8Array(MLDSA_SIGNATURE_SIZE);
await expect(sig.verify(message, signature)).rejects.toThrow('PQ crypto not available');
});
it('should throw on sign without secret key', async () => {
const publicKey = new Uint8Array(MLDSA_PUBLIC_KEY_SIZE);
const sig = PQSignature.fromPublicKey(publicKey);
const message = new Uint8Array([1, 2, 3]);
// PQ check happens first
await expect(sig.sign(message)).rejects.toThrow('PQ crypto not available');
});
});