xChange: XorIDA Key Transport
Replaces KEM key agreement with a random AES-256-GCM key bundled with ciphertext, then split via XorIDA. No lattice assumptions. Unconditionally quantum-safe. ~180x faster than KEM.
The KEM Bottleneck
ML-KEM-768 key agreement adds ~2,740µs overhead per message. For high-throughput API payloads, this latency dominates total processing time.
Key Encapsulation Mechanisms (KEMs) like ML-KEM-768 (Kyber) rely on the computational hardness of the Module Learning With Errors (MLWE) problem. While considered post-quantum secure, this is a computational assumption — not an unconditional guarantee. A mathematical breakthrough or sufficiently powerful quantum algorithm could invalidate the assumption retroactively.
Every post-quantum messaging platform in production today — Signal (PQXDH), Apple PQ3, Tuta — depends on Kyber lattice assumptions for key agreement. The payload encryption itself (AES or ChaCha) is symmetric, but the key transport layer is the single point of cryptographic assumption. If the lattice breaks, the key breaks, and every message encrypted under that key is exposed.
The Old Way
The xChange Solution
Generate a random AES-256-GCM key, encrypt the payload, bundle key+IV+ciphertext, and split the bundle via XorIDA. Each channel carries a share. No lattice assumptions. Information-theoretic security.
xChange eliminates the key agreement step entirely. Instead of negotiating a shared secret through a computationally hard problem, it generates a fresh random AES-256-GCM key per message using crypto.getRandomValues(). The payload is encrypted with this key, then the key, IV, and ciphertext are bundled together into a single binary blob.
This bundle is then split via XorIDA into N shares distributed across independent channels. No single channel carries enough information to reconstruct the bundle. The security comes from the information-theoretic properties of the split — not from the hardness of any mathematical problem. There is no lattice, no elliptic curve, no factoring assumption. The guarantee holds against unbounded adversaries, including quantum computers of any size.
The New Way
Architecture
The xChange bundle format packs the random AES-256-GCM key, initialization vector, and authenticated ciphertext into a single binary envelope that XorIDA splits atomically.
Bundle Format
Data Flow
import { generateXchangeKey, xchangeEncrypt, xchangeDecrypt } from '@private.me/xchange'; // Generate random AES-256-GCM key const key = await generateXchangeKey(); // Encrypt and bundle const bundle = await xchangeEncrypt(plaintext, key.value); // After XorIDA split + reconstruct on other side: const decrypted = await xchangeDecrypt(reconstructedBundle);
Benchmarks
xChange eliminates KEM overhead entirely. For API-sized payloads, total processing time drops by up to 2x compared to KEM-based competitors.
End-to-End Latency Comparison
| Payload | xLink (xChange) | Tuta | Signal | Apple PQ3 |
|---|---|---|---|---|
| 64B (auth token) | 2.0ms | 4.2ms | 5.0ms | 5.0ms |
| 256B (JSON) | 1.9ms | 4.2ms | 5.0ms | 5.0ms |
| 1KB (REST) | 1.9ms | 4.2ms | 5.0ms | 5.0ms |
| 4KB (document) | 2.5ms | 4.2ms | 5.0ms | 5.0ms |
xChange vs KEM Overhead
| Metric | xChange | KEM (ML-KEM-768) |
|---|---|---|
| Key generation | ~15µs | ~2,740µs |
| API latency overhead (10ms) | 16.2% | 39.7% |
| Throughput | 517 req/s | 151 req/s |
| Security model | Information-theoretic | Lattice computational |
| PQ protection | Unconditional | ML-KEM assumption |
Key Generation: xChange vs KEM
Security Analysis
xChange achieves information-theoretic confidentiality through XorIDA splitting and authenticated encryption through AES-256-GCM. No computational assumptions are required for the key transport layer.
| Property | Mechanism | Guarantee |
|---|---|---|
| Confidentiality | AES-256-GCM + XorIDA split | Information-theoretic |
| Integrity | GCM 16-byte auth tag | Tamper detection |
| Key freshness | Random key per message | No key reuse |
| IV uniqueness | crypto.getRandomValues() | No nonce reuse |
| Quantum safety | No lattice assumptions | Unconditional |
| Fail-closed | Auth tag check before output | No partial leakage |
Integration
xChange is used by @private.me/agent-sdk v4 envelopes for split-channel messaging. When both parties support xChange, it replaces KEM automatically. Fallback to KEM is seamless when the recipient does not advertise xChange support.
The agent SDK auto-selects xChange when the recipient's DID document includes xchange: true in its capabilities. No manual configuration is needed. The sender's agent queries the trust registry, checks capabilities, and routes through xChange or KEM accordingly. This makes the upgrade path zero-friction: deploy xChange support, and all future messages from xChange-capable senders use the faster, unconditionally secure path.
// Agent auto-selects xChange when recipient supports it const result = await alice.send({ to: bob.did, payload: { msg: 'secured via xChange' }, scope: 'email:send', splitChannel: true, splitChannelConfig: { totalShares: 3, threshold: 2 }, }); // Produces 3 v4 envelopes with kem: 'xChange'
xchange: true capability), the agent SDK falls back to v2/v3 envelopes using ML-KEM-768 + X25519 hybrid KEM. Existing deployments continue to work without any changes. xChange is an additive upgrade, not a breaking change.
Enhanced Identity with Xid
xChange can optionally integrate with Xid to enable unlinkable key transport — verifiable within each exchange context, but uncorrelatable across sessions, parties, or time.
Three Identity Modes
How Ephemeral Key Exchange Works
// Initialize xChange with Xid integration import { XchangeClient } from '@private.me/xchange-cli'; import { XidClient } from '@private.me/xid'; const xid = new XidClient({ mode: 'ephemeral' }); const xchange = new XchangeClient({ identityProvider: xid }); // Each exchange derives unlinkable DIDs automatically const { key, envelope } = await xchange.send(payload); → [xChange] Deriving ephemeral sender DID from master seed... → [xChange] Sender DID: did:key:z6MkH... (unique to this exchange + epoch) → [xChange] XorIDA-split AES key with ephemeral identity (~50µs) → [xChange] Key purged (<1ms exposure) // Exchange is unconditionally secure with unlinkable DIDs // Verification works within session, but cross-session correlation fails
See the Xid white paper for details on ephemeral identity primitives and K-of-N convergence.
Market Positioning
| Industry | Use Case | Compliance Driver |
|---|---|---|
| IoT | Sensor network key exchange with unlinkable device identities | GDPR, ePrivacy, IoT Security Act |
| Software Licensing (Anti-Piracy) | Unlinkable license key distribution to customer devices. Each activation derives ephemeral DIDs — prevents key tracking, blacklisting, and resale. Vendor ships to customers without exposing licensing infrastructure to piracy attacks. | IP protection, revenue assurance |
| Defense | Tactical communications with IAL3 party authentication | FISMA, DoD IL5/6, CMMC Level 3 |
| Finance | Payment network key agreement with PCI-compliant unlinkability | PCI-DSS, EMV, SWIFT CSP |
Key Benefits
- Cross-session unlinkability — Can't track parties across exchanges
- Per-exchange derivation — Same party has different DIDs per key agreement
- Epoch rotation — DIDs automatically rotate daily/weekly/monthly
- Split-protected derivation — Master seed is XorIDA-split, never reconstructed
- Single IT layer — ~180x faster than split-channel with hybrid PQ KEM
- IoT-optimized — ~1ms roundtrip for resource-constrained devices
API Reference
Three functions compose the entire xChange surface. Generate a key, encrypt a payload into a bundle, decrypt a bundle back to plaintext.
crypto.getRandomValues(). Returns a Result wrapping the key material. The key is single-use — generate a fresh key for every message.Error Codes
| Code | Trigger | Recovery |
|---|---|---|
| XCHANGE_KEYGEN_FAILED | crypto.getRandomValues() unavailable | Check runtime environment supports Web Crypto |
| XCHANGE_ENCRYPT_FAILED | AES-GCM encryption error | Verify key and plaintext are valid Uint8Array |
| XCHANGE_DECRYPT_FAILED | GCM auth tag mismatch | Bundle was tampered — reject and request retransmission |
| INVALID_BUNDLE:TOO_SHORT | Bundle < 44 bytes (missing header) | Verify bundle was not truncated during transport |
Constants
| Name | Value | Description |
|---|---|---|
| AES_KEY_BYTES | 32 | AES-256-GCM key length |
| AES_IV_BYTES | 12 | GCM initialization vector length |
| BUNDLE_HEADER_BYTES | 44 | Key (32) + IV (12) = minimum bundle size |
UX Enhancements
Session 64 UX standardization added progress tracking and detailed error handling to all xChange operations. Real-time progress callbacks and actionable error hints improve developer experience.
Progress Callbacks
All three core functions now accept optional ProgressCallback parameters that provide real-time status updates during operations. Useful for long-running operations or UI progress indicators.
import { generateXchangeKey, xchangeEncrypt } from '@private.me/xchange'; // Key generation with progress const key = await generateXchangeKey({ onProgress: (status, percent) => { console.log(`[${percent}%] ${status}`); } }); // [0%] Generating AES-256-GCM key... // [50%] Importing key material... // [100%] Complete // Encryption with progress const bundle = await xchangeEncrypt(plaintext, key.value, { onProgress: (status, percent) => { console.log(`[${percent}%] ${status}`); } }); // [0%] Preparing bundle encryption... // [30%] Encrypting plaintext with AES-256-GCM... // [70%] Bundling key + IV + ciphertext... // [100%] Complete
Detailed Error Information
The createXchangeErrorDetail function converts error codes into user-friendly messages with actionable hints, field attribution, and documentation links. All errors follow a standardized format across the PRIVATE.ME platform.
import { xchangeDecrypt, createXchangeErrorDetail } from '@private.me/xchange'; const result = await xchangeDecrypt(bundle); if (!result.ok) { const error = createXchangeErrorDetail(result.error); console.log(error.message); // User-friendly error message console.log(error.hint); // Actionable resolution hint console.log(error.field); // Field that caused error (if applicable) console.log(error.docs); // Documentation URL }
Error Details Map
All error codes map to structured error details with user-friendly messages, resolution hints, and documentation links:
| Code | Message | Hint |
|---|---|---|
| XCHANGE_KEYGEN_FAILED | AES-256-GCM key generation failed | crypto.getRandomValues or Web Crypto API unavailable. Ensure running in secure context (HTTPS). |
| XCHANGE_ENCRYPT_FAILED | Bundle encryption failed | AES-256-GCM encryption failed. Check key is valid and Web Crypto API is available. |
| XCHANGE_DECRYPT_FAILED | Bundle decryption failed | AES-256-GCM decryption failed. Data may be corrupted, tampered, or encrypted with different key. |
| INVALID_BUNDLE | Bundle format is invalid or unrecognized | Bundle data is corrupted or not a valid Xchange bundle. Check bundle reconstruction. |
| INVALID_BUNDLE:TOO_SHORT | Bundle is too short (minimum 60 bytes required) | Bundle must contain at least 32B key + 12B IV + 16B auth tag. Check XorIDA reconstruction produced full bundle. |
Integration Pattern: Progress + Error Handling
import { generateXchangeKey, xchangeEncrypt, xchangeDecrypt, createXchangeErrorDetail } from '@private.me/xchange'; import { split, reconstruct } from '@private.me/shareformat'; async function sendSecureMessage(plaintext: Uint8Array) { // Generate key with progress const keyResult = await generateXchangeKey({ onProgress: (status, percent) => updateUI(status, percent) }); if (!keyResult.ok) { const error = createXchangeErrorDetail(keyResult.error); throw new Error(`${error.message}: ${error.hint}`); } // Encrypt with progress const bundleResult = await xchangeEncrypt(plaintext, keyResult.value, { onProgress: (status, percent) => updateUI(status, percent) }); if (!bundleResult.ok) { const error = createXchangeErrorDetail(bundleResult.error); throw new Error(`${error.message}: ${error.hint}`); } // Split via XorIDA (2-of-3) const shares = split(bundleResult.value, { k: 2, n: 3 }); // Transport shares... return shares; }
Verifiable Data Protection
Every operation in this ACI produces a verifiable audit trail via xProve. HMAC-chained integrity proofs let auditors confirm that data was split, stored, and reconstructed correctly — without accessing the data itself.
Read the xProve white paper →
Use Cases
Four deployment patterns where xChange’s sub-millisecond latency unlocks capabilities that traditional encryption cannot support.
| Use Case | Why xChange | Key Metric |
|---|---|---|
| IoT fleet telemetry | Sub-millisecond split means sensor data is protected without adding measurable latency to telemetry pipelines. Traditional TLS handshakes add 50–200ms per connection. | ~1ms send/recv vs ~150ms TLS |
| Financial API security | Information-theoretic security for high-frequency trading data. No lattice assumptions, no key compromise risk. Regulatory compliance (DORA Article 9) via split-storage. | <1ms per API payload |
| Edge AI model inference | Protect inference requests and responses at the edge without GPU overhead. XorIDA splitting is CPU-only and parallelizable across cores. | ~35µs for 256B inference payload |
| Real-time messaging | 180x faster than split-channel V3. Users experience zero perceptible encryption delay. Group messages split once, delivered per-recipient. | ~1ms vs ~180ms (V3 with PQ sigs) |
Ship Proofs, Not Source
xChange generates cryptographic proofs of correct execution without exposing proprietary algorithms. Verify integrity using zero-knowledge proofs — no source code required.
- Tier 1 HMAC (~0.7KB)
- Tier 2 Commit-Reveal (~0.5KB)
- Tier 3 IT-MAC (~0.3KB)
- Tier 4 KKW ZK (~0.4KB)
Use Cases
Honest Limitations
Five known limitations documented transparently. xChange optimizes for speed by trading off features that split-channel V3 provides.
| Limitation | Impact | Mitigation |
|---|---|---|
| No per-message forward secrecy | xChange uses a single AES-256-GCM session key for all messages. Compromising the key compromises all messages in the session, not just one. | Session keys are XorIDA-split — compromise requires both shares simultaneously. For forward secrecy, use split-channel V3 (separate KEM per message). |
| Fixed 256-bit AES key | The session key is always AES-256-GCM. No algorithm agility — cannot switch to ChaCha20 or other ciphers without protocol changes. | AES-256-GCM is NIST-approved and hardware-accelerated on all modern CPUs. The XorIDA split layer provides information-theoretic security regardless of the symmetric cipher. |
| 2-of-2 only — no fault tolerance | xChange splits into exactly 2 shares. If either share is lost, the message is unrecoverable. No redundancy. | For fault-tolerant delivery, use split-channel V3 with 2-of-3 or 3-of-5 configurations. xChange prioritizes speed over redundancy. |
| No key negotiation (transport only) | xChange is a transport protocol, not a key agreement protocol. It requires an existing authenticated channel (xLink) for initial key exchange. | xLink handles identity, authentication, and key agreement. xChange handles fast data transport. The separation of concerns is intentional. |
| Single-threaded throughput | Current implementation processes messages sequentially. High-throughput scenarios (>10K msg/sec) may hit Node.js event loop limits. | XorIDA splitting is CPU-bound and parallelizable. Worker thread pool implementation is planned for high-throughput deployments. |
xChange Enterprise CLI
Self-hosted key transport server. Deploy xChange on your own infrastructure with Docker-ready, air-gapped capable deployment.
Features
Standalone HTTP server on port 4900 with 9 REST endpoints covering the complete key transport lifecycle. Built-in RBAC with three roles (admin, operator, auditor), JSONL-based audit logging, and sliding-window rate limiting (1,000 req/min per API key). All state stored in AES-256-GCM encrypted JSONL files for air-gap compatibility.
API Endpoints
| Endpoint | Method | Role | Purpose |
|---|---|---|---|
| /health | GET | Public | Server health check (uptime, version) |
| /keys/generate | POST | Operator | Generate random AES-256-GCM key |
| /encrypt | POST | Operator | Encrypt plaintext and create bundle |
| /decrypt | POST | Operator | Decrypt bundle to plaintext |
| /keys/:id | GET | Operator | Retrieve stored key by ID |
| /keys/:id | DELETE | Admin | Delete stored key |
| /audit | GET | Auditor | Query audit log (filter by action, user, date) |
| /apikeys/create | POST | Admin | Create new API key with role assignment |
| /apikeys/revoke | POST | Admin | Revoke existing API key |
RBAC Roles
- Admin: Create/revoke API keys, delete stored keys, full system control
- Operator: Generate keys, encrypt/decrypt, read keys, normal operations
- Auditor: Read-only access to audit logs, compliance review
Deployment
version: '3.8' services: xchange: image: privateme/xchange-cli:latest ports: - "4900:4900" volumes: - ./data:/data environment: - XCHANGE_PORT=4900 - XCHANGE_DATA_DIR=/data - ADMIN_API_KEY=${ADMIN_KEY}
Air-gap deployment: JSONL stores persist to local filesystem. No external database dependencies. Keys encrypted at rest with AES-256-GCM derived from admin API key. Rate limiter tracks per-key request windows in memory (survives restarts via JSONL replay).
Security Features
- API key authentication: All endpoints (except /health) require Bearer token
- Rate limiting: 1,000 requests per minute per API key (sliding window)
- Audit logging: Every operation logged to JSONL with timestamp, user, action, outcome
- Encrypted storage: Keys stored AES-256-GCM encrypted in JSONL files
- TTL expiry: Keys auto-expire after configurable duration (default 24h)
- Zero external deps: Runs offline, no database, no Redis, no external services
Fast Onboarding: 3 Acceleration Levels
Start transporting keys in under 2 minutes. Three acceleration levels: Node.js starter, serverless edge function, or one-click multi-platform deploy.
Level 1: Node.js TypeScript Starter
Clone and run. Demonstrates full Xchange lifecycle: key generation, encrypt, bundle, split, reconstruct, decrypt.
# Install dependencies npm install # Run demo npm run dev # Output: # 🔑 Generating key... # [0%] Generating AES-256-GCM key... # [100%] Complete # 🔐 Encrypting and bundling... # [0%] Preparing bundle encryption... # [30%] Encrypting plaintext with AES-256-GCM... # [100%] Complete # 🔀 Splitting bundle (2-of-3)... # ✅ Created 3 shares # 📡 Simulating multi-channel transport: # Share 0: 128 bytes → Email # Share 1: 128 bytes → SMS # Share 2: 128 bytes → Messenger
import { generateXchangeKey, xchangeEncrypt, xchangeDecrypt, } from '@private.me/xchange'; import { split, reconstruct } from '@private.me/shareformat'; // 1. Generate random AES-256-GCM key const keyResult = await generateXchangeKey(); // 2. Encrypt plaintext and bundle key + ciphertext const plaintext = new TextEncoder().encode('Secret message'); const bundleResult = await xchangeEncrypt(plaintext, keyResult.value); // 3. Split bundle via XorIDA (2-of-3 threshold) const shares = split(bundleResult.value, { k: 2, n: 3 }); // 4. Transport shares via different channels await sendViaEmail(recipient, shares[0]); await sendViaSMS(recipient.phone, shares[1]); await sendViaMessenger(recipient.id, shares[2]); // 5. Reconstruct (any 2 shares) const reconstructed = reconstruct([shares[0], shares[2]]); // 6. Decrypt bundle const decryptResult = await xchangeDecrypt(reconstructed); const decrypted = new TextDecoder().decode(decryptResult.value); // → "Secret message"
Level 2: Vercel Edge Function
Deploy to Vercel in ~15 seconds. REST API with encrypt/decrypt endpoints. Automatic scaling, global edge network.
# Deploy to Vercel npm i -g vercel vercel deploy --prod # Your Xchange API is live at: # https://your-app.vercel.app/api/xchange
import { generateXchangeKey, xchangeEncrypt } from '@private.me/xchange'; import { split } from '@private.me/shareformat'; export default async function handler(req, res) { const { action, data, shares } = req.body; if (action === 'encrypt') { const plaintext = new TextEncoder().encode(data); const keyResult = await generateXchangeKey(); const bundleResult = await xchangeEncrypt(plaintext, keyResult.value); // Split into shares (2-of-3 configurable) const threshold = parseInt(process.env.XCHANGE_THRESHOLD || '2'); const totalShares = parseInt(process.env.XCHANGE_TOTAL_SHARES || '3'); const splitShares = split(bundleResult.value, { k: threshold, n: totalShares }); res.json({ ok: true, shares: splitShares, threshold, totalShares }); } }
# Encrypt data curl -X POST https://your-app.vercel.app/api/xchange \ -H "Content-Type: application/json" \ -d '{"action":"encrypt","data":"Secret message"}' # Returns: { "ok": true, "shares": [[...], [...], [...]], "threshold": 2, "totalShares": 3 }
Level 3: One-Click Multi-Platform Deploy
GitHub starter repo with pre-configured deployment buttons for Vercel, Netlify, Railway. Platform-agnostic handler, deploy to any serverless provider in ~15-30 seconds.
# Clone starter repo git clone https://github.com/private-me/xchange-starter.git cd xchange-starter # Option 1: Deploy to Vercel (click button in README) # Option 2: Deploy to Netlify (click button in README) # Option 3: Deploy to Railway (click button in README) # Or run locally: npm install npm run dev:vercel # or dev:netlify
import { generateXchangeKey, xchangeEncrypt, xchangeDecrypt } from '@private.me/xchange'; import { split, reconstruct } from '@private.me/shareformat'; export async function handleXchangeRequest(request) { const { action, data, shares } = request; if (action === 'encrypt') { const plaintext = typeof data === 'string' ? new TextEncoder().encode(data) : new Uint8Array(data); const keyResult = await generateXchangeKey(); const bundleResult = await xchangeEncrypt(plaintext, keyResult.value); const threshold = parseInt(process.env.XCHANGE_THRESHOLD || '2'); const totalShares = parseInt(process.env.XCHANGE_TOTAL_SHARES || '3'); const splitShares = split(bundleResult.value, { k: threshold, n: totalShares }); return { ok: true, shares: splitShares, threshold, totalShares }; } if (action === 'decrypt') { const shareBuffers = shares.map(s => new Uint8Array(s)); const bundle = reconstruct(shareBuffers); const decryptResult = await xchangeDecrypt(bundle); const plaintext = new TextDecoder().decode(decryptResult.value); return { ok: true, data: plaintext }; } }
| Platform | Setup Time | Cold Start | Free Tier |
|---|---|---|---|
| Vercel | ~15 seconds | ~50-100ms | 100GB bandwidth/month |
| Netlify | ~20 seconds | ~100-150ms | 100GB bandwidth/month |
| Railway | ~30 seconds | ~150-250ms | $5 credit/month |
| AWS Lambda | ~60 seconds | ~200-400ms | 1M requests/month |
packages/xchange/templates/:
node-typescript/— Local development starter (Level 1)vercel/— Vercel Edge Function (Level 2)github-starter/— Multi-platform deploy (Level 3)
Why Fast Onboarding Matters
Xchange competes with established KEM solutions (ML-KEM-768, X25519). The performance advantage (~180x faster) is only valuable if developers can try it immediately. Three acceleration levels serve three developer profiles:
- Level 1 (Node.js): Backend engineers who want to understand the full encrypt-split-reconstruct-decrypt cycle before committing.
- Level 2 (Vercel): Full-stack developers who need a production-ready REST API in under a minute. Edge deployment, auto-scaling.
- Level 3 (Multi-platform): DevOps teams evaluating multiple cloud providers. Same handler logic, different runtime wrappers.
Traditional KEM integration requires key generation, trust anchor setup, certificate management, and integration testing. Xchange eliminates all of that. Generate a random key, bundle with ciphertext, split, send. No PKI, no trust anchors, no key escrow. The simplicity is the feature.
Ready to deploy xChange?
Talk to Ren, our AI sales engineer, or book a live demo with our team.
Deployment Options
SaaS Recommended
Fully managed infrastructure. Call our REST API, we handle scaling, updates, and operations.
- Zero infrastructure setup
- Automatic updates
- 99.9% uptime SLA
- Pay per use
SDK Integration
Embed directly in your application. Runs in your codebase with full programmatic control.
npm install @private.me/xchange- TypeScript/JavaScript SDK
- Full source access
- Enterprise support available
On-Premise Enterprise
Self-hosted infrastructure for air-gapped, compliance, or data residency requirements.
- Complete data sovereignty
- Air-gap capable
- Docker + Kubernetes ready
- RBAC + audit logs included