Replace any API connection in minutes
No API keys. No rotation. No downtime. Connect systems using cryptographic identity instead of bearer tokens. Run alongside your existing API, shift traffic gradually, remove keys when ready.
connect → send
no API keys, ever
run alongside, switch when ready
Deploy in 15 Seconds
Production-ready Full Control setup for paying customers
Pattern 1: Zero-Config Connect (DEFAULT)
const conn = await connect('payments')
await conn.value.agent .send({ to, payload })
Zero-config service discovery in 2 steps
connect → send
Zero-config pattern for AI agents, microservices, and IoT. 90% of users start here.
Pattern 2: Manual Setup (Advanced)
For advanced users who need full control over registry and transport configuration:
const agent = await Agent.quickstart()
~1ms • Zero config
await agent.send({ to: targetDid, payload: { type: 'ping' }, scope: 'read' })
One-time setup
const msg = await agent.receive(envelope)
Decrypts payload
Entity-to-entity authentication in 3 steps
Entity ↔ Entity. Not client → server.
Both have identity. Both verify. Both enforce scope.
Entity ↔ Entity. Not client → server.
Both have identity. Both verify. Both enforce scope.
Entity ↔ Entity Communication
Traditional APIs establish asymmetric connections: client (API key) → server (identity). The client authenticates with a secret key, the server has an identity. This creates an inherent power imbalance and single point of compromise.
Xlink establishes symmetric connections: Entity A (identity) ↔ Entity B (identity). Both sides generate cryptographic identities (Ed25519 signing + X25519 key agreement). Both sides sign outgoing messages and verify incoming messages. Both sides enforce scopes and replay protection. Neither side "owns" the connection — it's a peer-to-peer trust relationship between two independent entities.
| Property | Traditional API (Client → Server) | Xlink ACI (Entity ↔ Entity) |
|---|---|---|
| Client identity | API key (bearer token, stateless) | Cryptographic DID (Ed25519 keypair) |
| Server identity | Domain + TLS cert (DNS-based) | Cryptographic DID (same as client) |
| Authentication | Client proves possession of key | Both sides verify signatures (mutual) |
| Authorization | Server-side policy (RBAC, scopes) | Both sides enforce scopes (bilateral) |
| Message integrity | HTTPS (transport-level only) | Per-message signatures (end-to-end) |
| Replay protection | None (stateless tokens) | Nonce store on both sides |
| Compromise radius | Leaked key = global access | Compromised identity = single peer affected |
| Rotation | Ongoing (keys expire, rotate) | One-time setup (identity permanent) |
| Centralized gateway | Required (API gateway, auth server) | Optional (direct peer-to-peer works) |
| Dependency on DNS/PKI | Yes (domain name + CA trust chain) | No (cryptographic DIDs, no DNS) |
The shift from client-server to entity-to-entity removes the asymmetry: both sides have identity, both sides verify, both sides enforce policy. No "client" vs "server" — just two independent entities with a bilateral trust relationship.
Executive Summary
Xlink establishes entity-to-entity connections where both sides have cryptographic identities and verify each other's messages. No API keys, no asymmetric trust, no centralized gateways — just two independent entities with a bilateral trust relationship.
Two functions cover 80% of use cases: Agent.quickstart() generates an Ed25519 + X25519 identity with hybrid post-quantum key exchange (X25519 + ML-KEM-768, always-on) and registers it with default settings — zero configuration required. agent.send() encrypts a payload with AES-256-GCM, signs it with Ed25519 (+ ML-DSA-65 when postQuantumSig: true), and delivers it via any transport adapter. The receiver verifies signatures, checks replay protection, validates scope, and decrypts — automatically. Both sides perform the same verification steps, enforcing mutual trust rather than client-server hierarchy. For advanced use cases, Agent.create() accepts custom registry and transport configuration.
When you need information-theoretic security, split-channel mode shards messages via XorIDA (threshold sharing over GF(2)) and routes each share independently. An attacker who compromises any single channel learns nothing about the plaintext — not computationally hard to break, but mathematically impossible.
This entity-to-entity model removes the architectural assumptions of traditional APIs: no bearer tokens to rotate, no central auth servers to scale, no DNS dependency for trust. Each entity generates its own identity once, registers trusted peers, and communicates directly. The connection is symmetric, decentralized, and quantum-resistant by default.
Zero configuration out of the box. Zero npm runtime dependencies. Runs anywhere the Web Crypto API is available — Node.js, Deno, Bun, Cloudflare Workers, browsers. Dual ESM and CJS builds ship in a single package.
Multi-Agent Communication
Two agents establish cryptographic identities and communicate with full mutual authentication — no API keys, no central gateway, no configuration.
Alice and Bob: Peer-to-Peer Messaging
The following example demonstrates the complete lifecycle: both agents create identities using Agent.quickstart(), register with a shared trust registry, exchange messages with automatic encryption and signature verification, and validate each other's scope permissions.
import { Agent, MemoryTrustRegistry, LoopbackTransport } from '@private.me/agent-sdk'; // Shared infrastructure (in production, use HttpTrustRegistry + HttpsTransportAdapter) const registry = new MemoryTrustRegistry(); const transport = new LoopbackTransport(); // Alice creates her identity const alice = await Agent.quickstart({ name: 'Alice', registry, transport, }); console.log(`Alice DID: ${alice.did}`); // Alice DID: did:key:z6MksZP8ChwZYSNgozYq... // Bob creates his identity const bob = await Agent.quickstart({ name: 'Bob', registry, transport, }); console.log(`Bob DID: ${bob.did}`); // Bob DID: did:key:z6MkpH7eQ2KvYnPWbD... // Alice sends a message to Bob const sendResult = await alice.send({ to: bob.did, payload: { type: 'greeting', text: 'Hello Bob!' }, scope: 'chat', }); if (!sendResult.ok) { throw new Error(`Send failed: ${sendResult.error}`); } // Bob receives and verifies the message const envelope = transport.outbox[0]; const receiveResult = await bob.receive(envelope); if (!receiveResult.ok) { throw new Error(`Receive failed: ${receiveResult.error}`); } console.log(`From: ${receiveResult.value.sender}`); // From: did:key:z6MksZP8ChwZYSNgozYq... console.log(`Payload:` , receiveResult.value.payload); // Payload: { type: 'greeting', text: 'Hello Bob!' } console.log(`Scope: ${receiveResult.value.scope}`); // Scope: chat // Bob replies to Alice await bob.send({ to: alice.did, payload: { type: 'response', text: 'Hi Alice!' }, scope: 'chat', }); // Alice receives Bob's reply const reply = await alice.receive(transport.outbox[1]); console.log(`Bob says: ${reply.value.payload.text}`); // Bob says: Hi Alice!
Service-to-Service Communication
The same pattern applies to backend services. Here's a Payment service communicating with a Billing service:
// Shared infrastructure const registry = new HttpTrustRegistry({ baseUrl: 'https://trust.corp.example.com' }); const transport = new HttpsTransportAdapter({ baseUrl: 'https://relay.corp.example.com' }); // Payment service creates its identity const paymentService = await Agent.quickstart({ name: 'payment-service', registry, transport, }); // Billing service creates its identity const billingService = await Agent.quickstart({ name: 'billing-service', registry, transport, }); // Payment service notifies Billing about a completed transaction await paymentService.send({ to: billingService.did, payload: { transactionId: 'txn_abc123', amount: 99.50, currency: 'USD', customerId: 'cust_xyz789', }, scope: 'payment:notify', }); // Billing service receives and processes the notification const txn = await billingService.receive(envelope); console.log(`Transaction from: ${txn.value.sender}`); console.log(`Amount: $${txn.value.payload.amount}`); // Billing service sends confirmation back to Payment service await billingService.send({ to: paymentService.did, payload: { status: 'recorded', invoiceId: 'inv_2024_001', }, scope: 'billing:confirm', });
Agent.quickstart() handles identity generation, key agreement setup (X25519 + ML-KEM-768), registry registration, and transport initialization. Both agents use the same API — no distinction between "client" and "server". The connection is symmetric, peer-to-peer, and quantum-resistant by default.
What Happens Under the Hood
When Alice calls send():
- Lookup: Queries the trust registry to retrieve Bob's public keys (Ed25519 signing, X25519 key agreement, ML-KEM-768 post-quantum KEM)
- Key Agreement: Performs hybrid ECDH (X25519 + ML-KEM-768 encapsulation) to derive a shared symmetric key
- Encryption: Encrypts the payload with AES-256-GCM using the derived key
- Signing: Signs the envelope with Alice's Ed25519 private key
- Transport: Sends the signed envelope to Bob via the transport adapter
When Bob calls receive():
- Signature Verification: Verifies Alice's Ed25519 signature using her public key from the registry
- Replay Protection: Checks the nonce store to ensure this envelope hasn't been seen before
- Scope Validation: Confirms Alice has permission to use the specified scope
- Key Agreement: Performs hybrid ECDH (X25519 + ML-KEM-768 decapsulation) to derive the same shared key
- Decryption: Decrypts the payload with AES-256-GCM
- Returns Result: Delivers the verified, decrypted payload to the application
Both sides perform the same verification steps — mutual authentication, mutual scope enforcement, mutual replay protection. Neither side has elevated privileges. The connection is truly peer-to-peer.
Developer Experience
Xlink provides real-time progress tracking and 45+ structured error codes to help developers build reliable, debuggable M2M systems.
Progress Callbacks
Both send() and receive() operations support onProgress callbacks for tracking long-running operations, especially useful for split-channel mode where multiple shares are transmitted independently.
const envelope = await agent.send({ to: recipientDid, payload: largeData, onProgress: async (event) => { switch (event.stage) { case 'encrypting': console.log('Encrypting payload...'); break; case 'signing': console.log('Signing envelope...'); break; case 'sending': console.log(`Sending share ${event.current}/${event.total}...`); break; case 'complete': console.log('Message sent successfully'); break; } } }); // Receive with progress tracking const result = await agent.receive(envelope, { onProgress: async (event) => { if (event.stage === 'reconstructing') { console.log(`Reconstructing from ${event.current} shares...`); } } });
Structured Error Handling
Xlink uses a Result<T, E> pattern with detailed error structures. Every error includes a machine-readable code, human-readable message, actionable hint, and documentation URL.
interface ErrorDetail { code: string; // e.g., 'INVALID_DID' message: string; // Human-readable description hint?: string; // Actionable suggestion field?: string; // Field that caused the error docs?: string; // Documentation URL }
Error Categories
Xlink organizes 45+ error codes across 7 categories, making it easy to handle errors systematically:
| Category | Example Codes | When |
|---|---|---|
| Identity | INVALID_DID, KEYGEN_FAILED | DID validation, key generation, signing |
| Envelope | ENVELOPE_DECRYPTION_FAILED, PARSE_FAILED | Envelope creation, encryption, decryption |
| Transport | SEND_FAILED, NETWORK_ERROR | Network failures, transport adapter errors |
| Registry | DID_NOT_IN_REGISTRY, LOOKUP_FAILED | Trust registry operations |
| Key Agreement | ECDH_FAILED, INVALID_KEY_LENGTH | X25519 ECDH derivation |
| Split-Channel | HMAC_VERIFICATION_FAILED, INSUFFICIENT_SHARES | XorIDA splitting, reconstruction, HMAC |
| Agent | NONCE_REPLAY_DETECTED, SCOPE_DENIED | High-level agent operations, replay prevention |
Fast Onboarding: < 2 Minute Setup
Zero-config service discovery and invite flow enable rapid M2M adoption. Setup time: < 2 minutes (vs 42-67 minutes for API keys).
The 2-Minute Setup Flow
Traditional API key setup requires 42-67 minutes of developer time per integration: account creation, API key generation, secret management setup, documentation reading, SDK installation, configuration, testing, and deployment. Xlink reduces this to under 2 minutes through zero-config service discovery and automatic trust establishment:
// Step 1: Initialize local identity (< 30 sec) $ xlink init --name my-service { "status": "initialized", "did": "did:key:z6MksZP8ChwZYSNgozYq...", "name": "my-service" } // Step 2: Connect to a service (< 90 sec) $ xlink connect payments-service { "status": "connected", "service": "payments-service", "did": "did:key:z6Mkf2rR8...", "endpoint": "https://api.payments.example.com", "elapsed_seconds": 1.3 } // Step 3: Use it immediately const { connect } = require('@private.me/agent-sdk'); const connection = await connect('payments-service'); await connection.value.agent.send({ to: connection.value.did, payload: { action: 'createCharge', amount: 100 }, scope: 'payments' });
Zero-Config Discovery (3-Tier Lookup)
The connect() function accepts service names, domains, or URLs and automatically discovers connection details through a 3-tier lookup system:
| Method | Example | Lookup |
|---|---|---|
| Public Registry | connect('payments-service') |
Query xlink.registry.io for registered service |
| .well-known | connect('api.example.com') |
Fetch https://api.example.com/.well-known/xlink.json |
| Direct URL | connect('https://api.example.com/xlink') |
Use URL directly |
Invite Flow (< 10 sec creation, < 60 sec acceptance)
The invite system enables effortless service-to-service connections. Creating an invite takes < 10 seconds, accepting takes < 60 seconds, and the invite recipient can immediately use the connection.
$ xlink invite billing-service --email billing@example.com
{
"status": "created",
"invite_url": "https://xlink.to/invite/a7Km9x...",
"qr_code": "data:image/svg+xml,...",
"expires_at": "2026-04-19T...",
"message": "Share this link: https://xlink.to/invite/a7Km9x..."
}
When the recipient clicks the invite link, they see a one-click acceptance page with the inviter's service info. Accepting the invite automatically establishes the connection and adds both services to each other's trust registries.
Zero-Downtime Migration (Dual-Mode Adapter)
For existing M2M connections using API keys, Xlink provides a DualModeAdapter that runs Xlink and API key authentication simultaneously. This enables zero-downtime migration with gradual rollout and usage tracking:
const { DualModeAdapter } = require('@private.me/agent-sdk'); // Create dual-mode adapter (tries Xlink first, falls back to API key) const adapter = new DualModeAdapter({ xlink: xlinkAgent, // Optional: add when ready fallback: { type: 'api-key', key: process.env.API_KEY, url: 'https://api.example.com', }, }); // Make calls (automatically tries Xlink → API key) const result = await adapter.call('createCharge', { amount: 100 }); // Track migration progress const metrics = adapter.getMetrics(); console.log(`Xlink usage: ${metrics.xlinkPercentage}%`); // Output: "Xlink usage: 78%" // Remove fallback when 100% migrated adapter.removeFallback();
Comparison: Xlink vs Traditional API Keys
| Aspect | Traditional APIs | Xlink |
|---|---|---|
| Setup Time | 42-67 minutes (account + keys + config + docs + testing) | < 2 minutes (init + connect + use) |
| Secret Management | API keys in env vars, rotation every 90 days | No keys, zero rotation |
| Discovery | Manual documentation reading | Zero-config 3-tier lookup |
| Invite Mechanism | Email API key manually | One-click invite link, < 10 sec creation |
| Acceptance | Manual setup (42-67 min) | One-click acceptance (< 60 sec) |
| Network Effect | Linear (manual outreach) | Each connection enables further invites |
Three Paths to Production
Xlink adoption follows three distinct business paths: greenfield connections (new builds), migration (existing APIs), and enterprise deployment (governance at scale). Each path has its own optimal onboarding flow.
Path 2: Migration — Existing API connections. Run ACI parallel, shift traffic gradually via Xfuse.
Path 3: Enterprise — Large-scale deployment. Configure trust policies, audit trails, and governance.
Path 1: Greenfield (New Connections)
For new M2M connections with no existing API infrastructure, Xlink offers three speed tiers. All three accomplish the same goal — establishing a secure identity-based connection — but at different setup speeds and automation levels.
Speed Tier 1: Zero-Click (15 seconds)
Fastest onboarding for developers trying Xlink for the first time. Share an invite code, paste it into your environment variables, and the SDK auto-discovers the recipient's identity and auto-registers your DID. First send succeeds immediately with zero manual configuration.
// .env file XLINK_INVITE_CODE=XLK-abc123def456 // Your code const { Agent } = require('@private.me/agent-sdk'); // Agent auto-accepts invite and configures trust on first use const agent = Agent.lazy({ name: 'my-service' }); // First send triggers identity generation + auto-registration await agent.send({ to: 'did:key:z6MkPartnerDID...', payload: { action: 'processData', data: { ... } }, scope: 'integration' });
Setup time: ~15 seconds
Best for: First-time developers, rapid prototyping, quick demos
Key benefit: Instant working demo → share invite with colleagues → immediate connection
Speed Tier 2: CLI-Guided (90 seconds)
Interactive CLI command that guides developers through framework-specific setup. Generates boilerplate code for Node.js, Python, Go, or Rust. Validates the connection with a test message before completing.
# Both commands are equivalent (xlink-onboard is an alias) $ npx @private.me/agent-sdk init --invite XLK-abc123def456 # OR $ npx @private.me/agent-sdk xlink-onboard --invite XLK-abc123def456 ? Select your framework: Node.js (Express) ? Project name: my-integration ✓ Generated identity: did:key:z6MksZP8ChwZYSNgozYq... ✓ Configured trust registry ✓ Created src/xlink-client.ts ✓ Created .env with connection details ✓ Test message sent successfully Ready to integrate. Run: npm start
Setup time: ~90 seconds
Best for: Developers integrating into existing systems, production setup
Key benefit: Production-ready code generated, validated connection before completion
Speed Tier 3: Deploy Button (10 minutes)
One-click infrastructure deployment for teams. Provisions complete production environment with Xlink pre-configured — Docker containers, Nginx reverse proxy, SSL certificates, health checks, and monitoring. Outputs production URLs when complete.
<!-- Add to README.md --> [](https://github.com/private-me/xlink-infra/actions) // Click button → GitHub Actions provisions: // - DigitalOcean Droplet (or AWS EC2 / Google Cloud) // - Docker Compose with Xlink services // - Nginx reverse proxy + Let's Encrypt SSL // - Prometheus monitoring + Grafana dashboards // - Health check endpoints // Outputs after 10 minutes: { "service_url": "https://xlink.your-company.com", "did": "did:key:z6MksZP8ChwZYSNgozYq...", "status": "healthy" }
Setup time: ~10 minutes
Best for: Teams deploying production infrastructure, platform integrations
Key benefit: Complete infrastructure → SSL, monitoring, backups — zero DevOps work
Speed Tier Comparison
| Tier | Setup Time | Automation Level | Output | Best For |
|---|---|---|---|---|
| Zero-Click | 15 seconds | Full auto | Working connection | Demos, prototyping |
| CLI-Guided | 90 seconds | Interactive | Production code | Integration work |
| Deploy Button | 10 minutes | Infrastructure | Live service + monitoring | Team deployments |
Path 2: Migration (Existing APIs)
Already have a working API connection? Migrate safely to Xlink using Xfuse — the threshold identity fusion bridge that runs ACI connections in parallel with your existing API, shifts traffic gradually, and deprecates the API when you're ready.
// Step 1: Deploy ACI alongside existing API (no downtime) const { Xfuse } = require('@private.me/xfuse'); const bridge = await Xfuse.create({ legacy: { apiKey: process.env.API_KEY, endpoint: 'https://api.legacy.com' }, modern: { agent: xlinkAgent } }); // Step 2: Mirror traffic (validate both paths) const result = await bridge.send({ mode: 'mirror', // Send to both, compare results payload: { action: 'transfer', amount: 100 } }); // Step 3: Shift traffic (10% → 50% → 100%) bridge.setTrafficRatio({ aci: 0.5, api: 0.5 }); // Step 4: Deprecate API when ACI proves stable bridge.setTrafficRatio({ aci: 1.0, api: 0.0 });
Migration time: Days to weeks (gradual traffic shift)
Downtime: Zero (parallel deployment)
Rollback: Instant (shift ratio back to API)
Learn more: Xfuse White Paper
Path 3: Enterprise (Governance at Scale)
Large organizations require centralized trust management, audit trails, and policy enforcement across hundreds of services. Enterprise deployment focuses on governance infrastructure rather than individual connection speed.
// Step 1: Configure centralized trust registry const registry = await TrustRegistry.enterprise({ mode: 'centralized', endpoint: 'https://trust.corp.example.com' }); // Step 2: Define org-wide policies const policy = await PolicyEngine.create({ rules: [ { scope: 'finance', require: ['2FA', 'audit-log'] }, { scope: 'public', require: ['rate-limit'] } ] }); // Step 3: Enable audit trail for compliance const audit = await AuditLog.enterprise({ retention: '7-years', // SOX / SEC 17a-4 encryption: 'org-key' }); // All services inherit org config automatically const agent = await Agent.create({ name: 'payment-service', registry, // Shared across org policy, // Enforced centrally audit // Compliance copy to SOC });
Deployment scope: Organization-wide (10s–1000s of services)
Configuration: Once (all services inherit)
Compliance: SOC 2, ISO 27001, FedRAMP, HIPAA-ready
Learn more: Authorization • Audit Logs • Credentials
Choosing Your Path
Your adoption path depends on your starting point:
- Building something new? → Greenfield Path — Start with Zero-Click (15s) for instant demo, upgrade to CLI (90s) for production code, or use Deploy Button (10min) for full infrastructure.
- Already have an API? → Migration Path — Use Xfuse to run ACI parallel, shift traffic gradually (10% → 50% → 100%), deprecate API when stable. Zero downtime.
- Deploying across an organization? → Enterprise Path — Configure trust registry, policies, and audit once. All services inherit org-wide governance automatically.
The Problem
Machine-to-machine security today is a patchwork of API keys, OAuth client credentials, mTLS certificates, and API gateways — each with its own rotation schedule, configuration surface, and failure modes.
API keys leak. They end up in logs, git commits, environment variables shared over Slack, and CI pipelines with overly broad access. Rotation means touching every service that holds the key — a manual, error-prone process.
OAuth is complex. Client credentials flow requires token endpoints, scopes, refresh logic, and revocation. Every new service needs a registration, a secret, and a grant configuration.
mTLS certs expire. Certificate lifecycle management is a full-time job. Renewal failures cause outages. CA compromise is a single point of failure for the entire mesh.
Gateways add latency and cost. Centralized API gateways become bottlenecks, introduce single points of failure, and charge per-request fees that scale with traffic.
| Property | API Keys | OAuth 2.0 | mTLS | Xlink |
|---|---|---|---|---|
| Initial setup | Minutes | Hours | Days | 5 lines |
| Key rotation | Manual | Token refresh | Cert renewal | Never* |
| E2E encryption | No | No | Transport only | Yes |
| Forward secrecy | No | No | Optional | Auto ECDH |
| Non-repudiation | No | No | No | Ed25519 + ML-DSA-65 |
| Replay prevention | No | Partial | Partial | Nonce store |
| Info-theoretic mode | No | No | No | XorIDA split |
| npm dependencies | Varies | 10-50+ | OS-level | 0 |
* Ed25519 identity keys are permanent. No expiry, no renewal. New identity = new DID. See Limitations for details.
Two-Way Communication (vs One-Way APIs)
Traditional APIs provide one-way request/response flows. When bidirectional communication is needed, systems cobble together separate mechanisms: the client makes HTTP requests in one direction, and the server sends webhooks or uses SSE/WebSockets in the reverse direction. This creates architectural complexity, separate authentication flows, and webhook delivery failures.
Xlink provides native two-way peer-to-peer communication. Both services are agents with DIDs, public keys, and the ability to send and receive messages. There is no client/server distinction at the protocol level — both parties are peers. This eliminates the need for webhooks, polling, and separate bidirectional channels.
| Aspect | Traditional APIs | Xlink |
|---|---|---|
| Communication Model | Client → Server (one-way) | Peer ↔ Peer (native two-way) |
| Reverse Direction | Webhooks (separate HTTP POST, delivery failures, retries, auth) | Same protocol (send message to peer's DID) |
| Authentication | Two separate flows (API key for requests, webhook secret for callbacks) | Single mechanism (Ed25519 signatures both directions) |
| Delivery Guarantees | Best-effort webhooks, no built-in retry, manual dead-letter queues | Store-and-forward relay with 7-day TTL |
| Firewall Traversal | Webhook receiver must be publicly accessible (or use ngrok tunnels) | Both peers can be behind NAT/firewall (pull messages from relay) |
| Complexity | 2 separate subsystems (API client + webhook server) | 1 agent (send + receive) |
agent.send() and agent.receive() primitives regardless of direction.
// Service A sends request to Service B const request = await serviceA.send({ to: serviceBDid, payload: { action: 'processPayment', amount: 100 }, scope: 'payments', }); // Service B receives request const inbound = await serviceB.receive(request); // Service B sends response back to Service A const response = await serviceB.send({ to: serviceADid, payload: { status: 'success', transactionId: 'tx_123' }, scope: 'payments', }); // Service A receives response (no webhook needed) const result = await serviceA.receive(response);
With APIs, the reverse direction would require Service A to expose a webhook endpoint, implement authentication, handle delivery failures, and manage retries. With Xlink, both directions use the same authenticated message-passing primitives. The relay server handles store-and-forward, delivery guarantees, and NAT traversal automatically.
The Old Way
The New Way
Real-World Use Cases
Six scenarios where Xlink replaces traditional API key management with Authenticated Cryptographic Interfaces.
Each sensor gets a deterministic identity from a factory-burned seed. Signed telemetry envelopes flow to gateways. No API keys to rotate across 10,000 devices.
Agent.fromSeed() + createSignedEnvelope()AI agents negotiate tasks via encrypted envelopes with scope-based authorization. The orchestrator verifies each agent’s identity before dispatching work.
Agent.create() + agent.send() + scopesServices authenticate via DID instead of shared secrets. ECDH forward secrecy protects inter-service traffic. No certificate authority to manage.
HttpsTransportAdapter + MemoryTrustRegistryPHI travels via split-channel — any single intercepted channel reveals zero patient data. HIPAA compliance by mathematical guarantee, not policy alone.
security: 'high' (auto 2-of-3)Order routing with non-repudiation. Ed25519 signatures provide cryptographic proof of trade instructions. Timestamp validation prevents replay attacks.
Ed25519 non-repudiation + 30s windowSplit-channel with 3-of-5 threshold across classified and unclassified networks. Information-theoretic security exceeds AES-256 — quantum-proof by construction.
security: 'critical' (3-of-5)Solution Architecture
Six composable modules. Each can be used independently or combined through the high-level Agent ACI.
The One-Time Setup
Five lines of code. No configuration files, no gateway dashboards, no certificate authorities.
import { Agent, MemoryTrustRegistry, HttpsTransportAdapter } from '@private.me/agent-sdk'; const registry = new MemoryTrustRegistry(); const transport = new HttpsTransportAdapter({ baseUrl: 'https://api.example.com' }); const agent = (await Agent.create({ name: 'MyService', registry, transport })).value!; // That's it. agent.did is your identity. agent.send() encrypts + signs + delivers. await agent.send({ to: recipientDid, payload: { action: 'hello' }, scope: 'chat' });
Forward Secrecy
Hybrid post-quantum key agreement: X25519 ECDH + ML-KEM-768 KEM (always-on for v2+ agents). Ephemeral key pairs provide forward secrecy.
The sender generates a fresh ephemeral X25519 key pair per message. The shared secret is derived from the sender's ephemeral private key and the recipient's static X25519 public key. The ephemeral public key is included in the envelope so the receiver can derive the same shared secret.
Compromise of long-term keys does not reveal past messages. Each message uses a unique shared secret derived from a unique ephemeral key pair. Past messages are protected even if both parties' long-term keys are later compromised.
Split-Channel Mode
Information-theoretic security via XorIDA threshold secret sharing. Automatic risk-based activation.
The SDK automatically applies split-channel protection (2-of-3 XorIDA threshold sharing) for high-risk operations: high-value transfers, cross-organization communication, and sensitive scopes. The plaintext is split into N shares (default 3) over GF(2), with a reconstruction threshold of K (default 2). Each share is independently encrypted, signed, and transmitted via separate channels.
// SDK auto-applies split-channel for high-risk operations await agent.send({ to: recipientDid, payload: { amount: 500000, action: 'transfer' }, // High value → auto 2-of-3 scope: 'custody', // Sensitive scope → auto 2-of-3 action: 'execute' // Critical action → auto 2-of-3 }); // Manual override if needed (most users won't use this) await agent.send({ to: recipientDid, payload: { data: 'classified' }, scope: 'secure', security: 'high', // Force 2-of-3 even if policy wouldn't auto-apply });
splitChannel: true and splitChannelConfig flags are still supported but deprecated. New code should use security: 'auto' | 'standard' | 'high' | 'critical' for clearer intent.
Multi-Transport Routing
For true channel separation, provide one transport adapter per share. The SDK routes share[i] to transports[i % transports.length] using modulo arithmetic. With 3 shares and 3 transports, share 0 goes to transport 0, share 1 to transport 1, share 2 to transport 2. With 3 shares and 2 transports, shares route as 0→0, 1→1, 2→0 (wraps around).
const agent = await Agent.create({ name: 'SecureAgent', registry, transport: [ new HttpsTransportAdapter({ baseUrl: 'https://ch1.example.com' }), new HttpsTransportAdapter({ baseUrl: 'https://ch2.example.com' }), new HttpsTransportAdapter({ baseUrl: 'https://ch3.example.com' }), ], }); // Shares route: [0 → ch1, 1 → ch2, 2 → ch3] await agent.send({ to: recipientDid, payload: sensitiveData, scope: 'classified', security: 'high', // Auto 2-of-3 split across 3 transports });
Channel independence is the developer’s responsibility. For maximum security, use different infrastructure providers, different network paths, or different geographic regions for each transport. The SDK warns at runtime if transports.length < totalShares.
| Scenario | Shares | Transports | Routing | Security |
|---|---|---|---|---|
| Ideal | 3 | 3 | 0→0, 1→1, 2→2 | Full channel separation |
| Partial | 3 | 2 | 0→0, 1→1, 2→0 | Two channels (share reuse) |
| Single | 3 | 1 | 0→0, 1→0, 2→0 | No channel separation |
transport parameter accepts either a single XailTransportAdapter or an array of adapters. Passing a single adapter is equivalent to transport: [adapter] — all shares route through it. Use arrays to achieve true multi-path delivery. See distributed messaging patterns for routing architecture guidance.
V3 Protocol (Default for Split-Channel)
When split-channel mode is activated (via automatic policy or explicit security: 'high') without Xchange, the SDK uses V3 envelopes with full post-quantum protection and three independent cryptographic layers:
| Layer | Technology | Purpose | Standard |
|---|---|---|---|
| 1. Payload | XorIDA (GF(2)) | Information-theoretic splitting | Proprietary (patent-protected) |
| 2. Key Exchange | X25519 + ML-KEM-768 | Hybrid PQ session keys | FIPS 203 (always-on) |
| 3. Authentication | Ed25519 + ML-DSA-65 | Dual signature verification | FIPS 204 (opt-in via postQuantumSig: true) |
V3 is the default for split-channel mode. Each share is independently encrypted with AES-256-GCM using a session key derived from hybrid KEM. The sender generates an ephemeral X25519 key pair AND performs ML-KEM-768 encapsulation per message. Both shared secrets combine via HKDF-SHA256. Authentication uses Ed25519 (always) plus ML-DSA-65 (when enabled). V3 provides defense-in-depth: compromise of any single cryptographic primitive does not break the system.
Xchange Mode (Opt-In Performance)
For latency-critical workloads (IoT, high-frequency M2M, real-time agents), Xchange mode trades per-share encryption and KEM for ~180x faster operation. Activated explicitly via xchange: true on both agent creation and send.
// Agent opts in to Xchange support const agent = await Agent.create({ name: 'FastAgent', registry, transport, xchange: true, }); // Xchange on send (security policy still applies) await agent.send({ to: recipientDid, payload: sensorReading, scope: 'telemetry', security: 'high', // Split-channel with Xchange speed });
Default split-channel uses V3 with three independent cryptographic layers: XorIDA payload split, hybrid PQ KEM (X25519 + ML-KEM-768), and dual signatures (Ed25519 + ML-DSA-65). Xchange mode is for scenarios where latency matters more than defense-in-depth.
Identity Layers in PRIVATE.ME
Three composable identity layers. Xlink is Layer 1. XID adds ephemeral unlinkability. Xfuse adds threshold convergence for high-assurance scenarios.
The PRIVATE.ME platform provides a three-layer identity architecture. Each layer builds on the previous, offering progressively stronger privacy guarantees and multi-factor assurance. Applications choose the layer that matches their security requirements.
Layer 1: Xlink — Cryptographic Identity
Xlink provides the foundational identity layer. Every agent has a persistent DID (did:key:z6Mk...) backed by Ed25519 signing and X25519 key agreement. This is the identity used for most M2M communication, agent-to-agent messaging, and service authentication.
import { Agent } from '@private.me/agent-sdk'; // Create a persistent agent identity const agent = (await Agent.create({ name: 'MyService', registry, transport })).value!; // DID is stable across sessions console.log(agent.did); // did:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH // Same DID on every restart (if keys persisted) const pkcs8 = await agent.exportPKCS8(); const restored = (await Agent.importIdentity({ pkcs8, registry, transport })).value!; console.log(restored.did === agent.did); // true
Layer 2: XID — Ephemeral Unlinkable Identity
XID adds per-verifier ephemeral DIDs derived from a master seed via HKDF. Each relationship sees a different DID. Cross-context tracking becomes impossible. DIDs rotate on a configurable schedule (epoch-based). The master seed is XorIDA-split and never stored in plaintext.
import { EphemeralIdentity } from '@private.me/xid'; // Create ephemeral identity manager (master seed is split-protected) const eph = await EphemeralIdentity.create({ epochDurationMs: 86400000, // 24 hours splitConfig: { totalShares: 3, threshold: 2 } });// Derive ephemeral DID for specific verifier const did1 = await eph.deriveForVerifier('ServiceA'); const did2 = await eph.deriveForVerifier('ServiceB'); console.log(did1 !== did2); // true — unlinkable across contexts // DID rotates on epoch boundary const nextEpoch = await eph.deriveForVerifier('ServiceA', epoch + 1); console.log(nextEpoch !== did1); // true — unlinkable across time
See the XID white paper for full technical details on HKDF derivation schedules, epoch management, and split-protected seed storage.
Layer 3: Xfuse — Threshold Identity Convergence
Xfuse adds K-of-N threshold convergence for high-assurance scenarios. Identity is established by presenting K independent signals (password + biometric + device credential + trusted third party attestation). Signals converge via XorIDA to derive a session-bound DID. No single signal is sufficient.
import { FusionManager } from '@private.me/xfuse'; // Configure threshold identity fusion const fusion = new FusionManager({ threshold: 2, totalSignals: 3, ial: 'IAL2', // NIST 800-63A assurance level }); // Enroll three independent signals await fusion.enrollSignal({ type: 'password', value: hashedPassword }); await fusion.enrollSignal({ type: 'biometric', value: fingerprintTemplate }); await fusion.enrollSignal({ type: 'device', value: tpmAttestation }); // Authenticate with any 2 of 3 signals const result = await fusion.converge([ { type: 'password', value: hashedPassword }, { type: 'biometric', value: currentFingerprint }, ]); // Session-bound DID derived from converged signals console.log(result.did); // did:key:z6Mk... (ephemeral, session-scoped) console.log(result.assuranceLevel); // IAL2
See the Xfuse white paper for threshold convergence algorithms, signal diversity requirements, assurance level mapping (IAL1/IAL2/IAL3), and MPC-verified convergence.
Composability & Layer Selection
Applications select the identity layer at integration time. The three layers are independent but composable. A single codebase can use Layer 1 for internal services, Layer 2 for customer-facing endpoints, and Layer 3 for administrative access.
| Layer | Identity Model | Privacy Guarantee | Use Cases |
|---|---|---|---|
| Layer 1: Xlink | Persistent DID | Encrypted + signed messaging | M2M, agent communication, internal APIs |
| Layer 2: XID | Ephemeral per-verifier DID | Cross-context unlinkability | Customer apps, eIDAS compliance, GDPR |
| Layer 3: Xfuse | K-of-N threshold convergence | Multi-factor high-assurance | Defense, finance, healthcare, gov |
// Internal service — Layer 1 (persistent identity) const internalAgent = await Agent.create({ name: 'InternalService', registry, transport }); // Customer app — Layer 2 (ephemeral unlinkable) const customerEph = await EphemeralIdentity.create({ epochDurationMs: 3600000 }); const customerDid = await customerEph.deriveForVerifier('CustomerPortal'); // Admin access — Layer 3 (threshold convergence) const adminFusion = new FusionManager({ threshold: 3, totalSignals: 4, ial: 'IAL3' }); const adminResult = await adminFusion.converge(signals);
Layer 1 (Xlink) is the foundation. Layer 2 (XID) and Layer 3 (Xfuse) are optional enhancements. Most applications use Layer 1 exclusively. Add Layer 2 when unlinkability is required. Add Layer 3 when regulatory compliance demands multi-factor high-assurance identity.
Identity & Persistence
Ed25519 signing + X25519 key agreement via Web Crypto API. Hybrid post-quantum: ML-KEM-768 for key exchange (always-on), ML-DSA-65 for signatures (opt-in). Multiple persistence strategies for different environments.
DID:key Format
Each agent identity is encoded as a did:key DID string: did:key:z6Mk.... The DID embeds the raw Ed25519 public key with a multicodec prefix (0xed01) and base58btc encoding. Anyone with the DID can verify signatures without a network lookup.
Persistence Strategies
Complete Flow
End-to-end: identity creation through message delivery and verification.
Sender Pipeline
1. Agent.create() generates Ed25519 + X25519 keys (+ ML-DSA-65 keys when postQuantumSig: true) and registers with the trust registry.
2. agent.send() resolves the recipient DID from the registry.
3. Hybrid key exchange: X25519 ECDH + ML-KEM-768 KEM, combined via HKDF-SHA256 (always-on for v2+ agents). SHA-256 fallback for v1 peers.
4. Payload encrypted with AES-256-GCM (12-byte IV, fresh per message).
5. Ciphertext signed with Ed25519 (+ ML-DSA-65 dual signature in v3 envelopes).
6. Envelope assembled: v2/v3, sender DID, recipient DID, timestamp, nonce, scope, payload, signature(s), ephemeralPub, kemCiphertext.
7. Transport adapter delivers the envelope.
Receiver Pipeline
8. agent.receive() validates envelope version (v1/v2/v3) and algorithm.
9. Timestamp checked against configurable window (default 30s).
10. Nonce checked against NonceStore — rejects duplicates (replay prevention).
11. Sender DID resolved from trust registry — must be registered and not revoked.
12. Ed25519 signature verified. ML-DSA-65 signature also verified if present (v3 envelopes).
13. Sender's scope validated against the claimed scope in the envelope.
14. Shared key derived via hybrid KEM (X25519 + ML-KEM-768) or SHA-256 fallback for v1.
15. Payload decrypted with AES-256-GCM.
16. JSON parsed and returned as AgentMessage.
Integration Patterns
Four patterns for different deployment contexts.
Express Middleware
import express from 'express'; const app = express(); // Verify incoming agent envelopes app.post('/api/messages', agent.middleware(), (req, res) => { const msg = req.agentMessage; console.log('From:', msg.sender, 'Scope:', msg.scope); res.json({ ok: true }); });
Registry Auth Middleware
import { createRegistryAuthMiddleware } from '@private.me/agent-sdk'; // GET /registry/resolve/:did → public (no auth) // POST /registry/register → requires Bearer token app.use('/registry', createRegistryAuthMiddleware(process.env.REGISTRY_ADMIN_TOKEN));
IoT Composable Pattern
import { generateIdentity, createSignedEnvelope, splitForChannel } from '@private.me/agent-sdk'; const id = (await generateIdentity()).value!; const reading = new TextEncoder().encode(JSON.stringify({ temp: 22.5 })); // Signed-only envelope (no encryption, integrity-only) const envelope = await createSignedEnvelope({ senderDid: id.did, recipientDid: gatewayDid, scope: 'telemetry', plaintext: reading, privateKey: id.privateKey, }); // Or split across channels for redundancy const shares = await splitForChannel(reading, { totalShares: 3, threshold: 2 });
Signed-Only Telemetry
// Gateway accepts both encrypted and signed-only envelopes const msg = await gateway.receive(envelope, { allowCleartext: true }); if (msg.ok) { console.log(msg.value.payload); // works for both modes }
Security Properties
Seven layers of defense. Each independently verifiable.
| Property | Mechanism | Guarantee |
|---|---|---|
| Confidentiality | AES-256-GCM per message | Payload encrypted in transit and at rest |
| Authentication | Ed25519 + ML-DSA-65 (dual) | Sender identity verified on every envelope (PQ-safe with opt-in) |
| Integrity | Encrypt-then-sign | Any modification fails verification |
| Non-repudiation | Ed25519 + ML-DSA-65 dual signature | Sender cannot deny sending (quantum-safe with opt-in) |
| Forward secrecy | X25519 + ML-KEM-768 hybrid | Post-quantum forward secrecy (always-on) |
| Replay prevention | Nonce store + timestamp | Duplicate envelopes rejected |
| Info-theoretic | XorIDA split-channel | K-1 shares reveal zero bits |
Supply Chain Security
Zero npm runtime dependencies. The SDK depends only on workspace packages (@private.me/shared, @private.me/crypto) which are part of the same monorepo. No third-party code executes at runtime. This eliminates the entire class of supply chain attacks from compromised npm packages.
Comparison to Alternatives
| Property | Xlink | TLS | Signal Protocol |
|---|---|---|---|
| E2E encryption | Yes | Transport only | Yes |
| Forward secrecy | Yes (Hybrid PQ KEM) | Yes (DHE) | Yes (Ratchet) |
| Info-theoretic | Yes (XorIDA) | No | No |
| Non-repudiation | Ed25519 + ML-DSA-65 | No | No |
| Replay prevention | Nonce store | Seq numbers | Chain keys |
| Zero npm deps | Yes | N/A | No |
| M2M focused | Yes | General | Person-to-person |
Benchmarks
Performance characteristics measured on Node.js 22, Apple M2.
| Operation | Time | Notes |
|---|---|---|
| Ed25519 keygen | <1ms | Web Crypto API native |
| X25519 keygen | <1ms | Web Crypto API native |
| ECDH key agreement | <1ms | Ephemeral + derive |
| AES-256-GCM encrypt (1KB) | <0.5ms | Hardware-accelerated |
| Ed25519 sign | <0.5ms | Covers ciphertext bytes |
| ML-KEM-768 encapsulate | ~2.7ms | FIPS 203 — @noble/post-quantum |
| ML-KEM-768 decapsulate | ~2.9ms | FIPS 203 — @noble/post-quantum |
| ML-DSA-65 sign | ~10ms | FIPS 204 — opt-in dual signature |
| ML-DSA-65 verify | ~10ms | FIPS 204 — opt-in dual signature |
| Full send pipeline (Xchange) | ~1ms | Random key + encrypt + XorIDA split + sign |
| Full receive pipeline (Xchange) | ~1ms | Verify + reconstruct + HMAC + decrypt |
| Full send pipeline (split-channel) | ~5ms | KEM + per-share AES-GCM + XorIDA split + Ed25519 |
| Full receive pipeline (split-channel) | ~4ms | Verify + KEM decaps + per-share decrypt + reconstruct |
| Full roundtrip (split-channel + ML-DSA-65) | ~29ms | Three layers + dual PQ signatures |
| XorIDA split (1MB, 3 shares) | ~52ms | GF(2) — 15x faster than Shamir's at 1MB |
| XorIDA reconstruct (1MB) | ~33ms | Including HMAC verification |
| Nonce check (memory) | <0.1ms | Map lookup + TTL check |
XorIDA vs AES-256-GCM — API Payload Performance
Typical ACI traffic — auth tokens, JSON responses, webhooks, chat messages — is under 1 KB. At these sizes, XorIDA’s simple XOR-only arithmetic completes a full split-and-reconstruct roundtrip 2–11× faster than AES-256-GCM can encrypt-and-decrypt, while providing strictly stronger (information-theoretic) security.
256-Byte Roundtrip — Visual Comparison
Shorter bar = faster. 256B payload, 2,000 iterations, median roundtrip time.
Full Comparison — API Payload Sizes
| Payload | XorIDA 2-of-2 | XorIDA 2-of-3 | AES-256-GCM | Ratio (2-of-2) | Real-World Example |
|---|---|---|---|---|---|
| 64B | 14µs | 17µs | 160µs | 11.4× faster | IoT sensor reading, heartbeat |
| 128B | 23µs | 26µs | 160µs | 7.0× faster | Auth token, session ticket |
| 256B | 35µs | 41µs | 122µs | 3.5× faster | Chat message, SMS-length payload |
| 512B | 34µs | 39µs | 138µs | 4.0× faster | Webhook, API key exchange |
| 1KB | 58µs | 107µs | 140µs | 2.4× faster | REST API JSON response |
| 2KB | 211µs | 262µs | 142µs | 1.5× slower | GraphQL response |
| 4KB | 340µs | 313µs | 134µs | 2.5× slower | Large API response |
| 8KB | 644µs | 883µs | 227µs | 2.8× slower | Crossover zone |
Node.js 22 • 2,000 iterations per size • Median roundtrip (split+reconstruct / encrypt+decrypt)
Xlink vs Competitors — API Crypto Overhead
Full send + receive roundtrip measured end-to-end with real crypto operations. Competitor estimates use our measured primitive timings (same algorithms, same JS runtime) as reference. All columns use Xchange mode (opt-in performance path) for an apples-to-apples comparison; Xlink’s default split-channel adds KEM + per-share encryption (~9ms roundtrip) for three independent security layers.
256-Byte API Payload — Full Roundtrip
Shorter bar = faster. 256B payload, 200 iterations, median roundtrip time.
Full Comparison — All API Payload Sizes
| Payload | Xlink (Xchange) | Tuta | Signal | Apple PQ3 | Use Case |
|---|---|---|---|---|---|
| 64B | 2.0ms | 4.2ms | 5.0ms | 5.0ms | Auth token, session ID |
| 128B | 1.8ms | 4.2ms | 5.0ms | 5.0ms | Webhook notification |
| 256B | 1.9ms | 4.2ms | 5.0ms | 5.0ms | Small JSON response |
| 512B | 1.8ms | 4.2ms | 5.0ms | 5.0ms | API request body |
| 1KB | 1.9ms | 4.2ms | 5.0ms | 5.0ms | Large JSON / config |
| 2KB | 1.9ms | 4.2ms | 5.0ms | 5.0ms | Batch API response |
| 4KB | 2.5ms | 4.2ms | 5.0ms | 5.0ms | Document payload |
| 8KB | 3.7ms | 4.3ms | 5.1ms | 5.1ms | Rich API response |
Node.js 22 • 200 iterations per size • Median send+receive roundtrip • 2-of-3 split
Crypto Overhead as % of Total API Latency (256B)
| Network Latency | Xlink (Xchange) | Tuta | Signal |
|---|---|---|---|
| Local (1ms) | 65.9% | 80.8% | 83.3% |
| Regional (10ms) | 16.2% | 29.6% | 33.3% |
| Cross-region (50ms) | 3.7% | 7.7% | 9.1% |
| Global (150ms) | 1.3% | 2.7% | 3.2% |
Xchange mode shown. Default split-channel overhead is higher (~9ms roundtrip). At 10ms+ latency, even split-channel overhead is modest.
Security vs Performance
Ranked by roundtrip speed at 256B. Xlink appears twice: Xchange (opt-in performance mode, single information-theoretic layer) and split-channel default (three independent layers including hybrid PQ KEM).
| # | System | Roundtrip | Security Model | Channels | PQ Protection |
|---|---|---|---|---|---|
| 1 | Xlink (Xchange) | 1.9ms | Information-theoretic | k-of-n split | ✓ Unconditional |
| 2 | Tuta | 4.2ms | Computational | Single | ✓ Kyber KEM only |
| 3 | Signal | 5.0ms | Hybrid computational | Single | ✓ Kyber KEM only |
| 4 | Apple PQ3 | 5.0ms | Hybrid computational | Single | ✓ Kyber KEM only |
| 5 | Xlink (split-channel) | ~9ms | IT + computational (3 layers) | k-of-n split | ✓ KEM + opt-in ML-DSA |
Honest Limitations
Eight known limitations documented transparently. No product is perfect — here is what Xlink does not do.
| Limitation | Impact | Mitigation |
|---|---|---|
| Cleartext headers | Sender/recipient DIDs, scope, timestamp visible to network observers. Traffic analysis possible. | Payload is encrypted. Use TLS for transport-level protection of headers. |
| No push revocation | Revoked agents' in-flight messages may be processed before receiver re-checks registry. | Keep timestamp windows short (default 30s). |
| No key rotation | Ed25519 identity keys are permanent. No built-in rotation protocol. | Create new identity and re-register. Old DID becomes unused. |
| SHA-256 fallback | When ECDH unavailable, shared key is deterministic. Not forward-secret. | Ensure both parties publish X25519 keys for automatic ECDH upgrade. |
| Ephemeral nonce store | MemoryNonceStore clears on process restart. Replays within timestamp window succeed. | Use RedisNonceStore for production deployments. |
| Clock dependency | Timestamp validation assumes synchronized clocks. Large skew causes false rejections. | Use NTP. Increase timestampWindowMs for high-latency networks. |
| No payload size limits | SDK does not enforce maximum payload size. Large payloads can exhaust memory. | Validate payload size at application layer before calling send(). |
| Registry trust | Compromised registry can substitute public keys or modify scopes. | Use createRegistryAuthMiddleware() with bearer token auth on writes. |
Post-Quantum Security
Xlink is end-to-end post-quantum. XorIDA payload splitting is information-theoretically quantum-safe. Key exchange uses hybrid X25519 + ML-KEM-768 (always-on, FIPS 203). Signatures use dual Ed25519 + ML-DSA-65 (opt-in via postQuantumSig: true, FIPS 204). All three cryptographic layers — payload, key exchange, and authentication — have quantum-safe implementations deployed.
Two Security Layers
The Xlink architecture has two distinct cryptographic layers with different quantum profiles:
| Layer | Current | Quantum Status | Upgrade Path |
|---|---|---|---|
| Payload (XorIDA) | GF(2) threshold sharing | Quantum-safe now | No change needed |
| Symmetric encryption | AES-256-GCM | Quantum-safe now | No change needed |
| Key establishment | X25519 + ML-KEM-768 (hybrid) | Hybrid PQ — Phase 1 live | Phase 1 deployed (v2 envelopes) |
| Identity / signatures | Ed25519 + ML-DSA-65 (dual) | Hybrid PQ — Phase 2 deployed (opt-in) | Phase 2 deployed (v3 envelopes) |
Migration Strategy
The upgrade follows a three-phase hybrid-first approach. Each phase maintains backward compatibility with the previous one.
Algorithm Profile
| Function | Algorithm | Standard | Key / Sig Size |
|---|---|---|---|
| KEM (Phase 1+) | ML-KEM-768 | FIPS 203 | 1,184 B pub / 1,088 B ct |
| Hybrid KEM | X25519 + ML-KEM-768 | IETF draft | HKDF over both shared secrets |
| Signature (Phase 2, deployed) | ML-DSA-65 | FIPS 204 | 1,952 B pub / 3,309 B sig |
| Symmetric | AES-256-GCM | NIST SP 800-38D | Unchanged |
| Payload splitting | XorIDA GF(2) | Proprietary | Unchanged |
Latency Impact
Post-quantum operations add minimal computational overhead. The dominant latency remains network I/O, not cryptography.
| Operation | Current (Classical) | Hybrid (Phase 1) | Delta |
|---|---|---|---|
| Key exchange | <0.1ms (X25519) | ~0.3ms (X25519 + ML-KEM) | +0.2ms |
| Sign | <0.1ms (Ed25519) | ~1.1ms (Ed25519 + ML-DSA) | +1.0ms |
| Verify | <0.2ms (Ed25519) | ~0.7ms (Ed25519 + ML-DSA) | +0.5ms |
| Full handshake | ~0.4ms | ~2.1ms | +1.7ms |
| Envelope overhead | ~128 bytes | ~6,340 bytes | +6.1 KB |
Envelope Version
The envelope format uses a v2 version tag signaling hybrid PQ support. Agents negotiate capabilities automatically:
- v1 agents communicate using classical crypto (X25519-only ECDH)
- v2 agents communicate using hybrid PQ + classical crypto (X25519 + ML-KEM-768)
- v2 agents automatically fall back to v1 when communicating with v1 peers
- v3 agents add dual signatures (Ed25519 + ML-DSA-65) — deployed, opt-in via
postQuantumSig: true
postQuantumSig: true).
Protocol Security Stack
The Xlink protocol secures messages at three independent cryptographic layers. Each layer addresses a different threat surface. Together, they provide full-stack quantum safety with no single point of cryptographic failure.
Three-Layer Architecture
| Layer | Function | Algorithm | Standard | Quantum Status |
|---|---|---|---|---|
| Payload | Message confidentiality | XorIDA threshold sharing over GF(2) | Proprietary | Immune |
| Key Exchange | Session key establishment | X25519 + ML-KEM-768 (hybrid) | FIPS 203 | Quantum-safe |
| Authentication | Identity verification & integrity | Ed25519 + ML-DSA-65 (dual) | FIPS 204 | Quantum-safe |
Layer 1 — Payload (Information-Theoretic)
XorIDA splits the plaintext into threshold shares using XOR operations over GF(2). Each share is individually indistinguishable from random noise. No computation — classical or quantum — can extract information from fewer than k shares. This is not computational hardness; it is a mathematical proof. The payload layer is immune to harvest-now-decrypt-later attacks because there is no key to break, no structure to exploit, and no algorithm that reduces the problem.
Layer 2 — Key Exchange (Hybrid Post-Quantum KEM)
Session keys are established using a hybrid key encapsulation mechanism: classical X25519 ECDH combined with ML-KEM-768 (FIPS 203, formerly CRYSTALS-Kyber). Both shared secrets are combined via HKDF-SHA256 to derive the session key. The session key is secure as long as either X25519 or ML-KEM-768 remains unbroken — defense in depth. Hybrid KEM is live in v2 envelopes (Phase 1, deployed).
Layer 3 — Authentication (Dual Signatures)
Message authentication and sender identity use dual signatures: classical Ed25519 plus post-quantum ML-DSA-65 (FIPS 204). Both signatures must verify for the message to be accepted. ML-DSA-65 signatures are opt-in via postQuantumSig: true in the agent configuration. When enabled, v3 envelopes carry both signatures. Classical-only agents continue to work with v1/v2 envelopes.
Envelope Version Progression
- v1 — Classical only. X25519 ECDH key exchange, Ed25519 signatures.
- v2 — Hybrid PQ KEM. X25519 + ML-KEM-768 key exchange, Ed25519 signatures. Backward-compatible with v1 peers.
- v3 — Full PQ. Hybrid KEM + dual signatures (Ed25519 + ML-DSA-65). Backward-compatible with v1/v2 peers. Default for split-channel.
- Xchange — Opt-in performance mode. XorIDA key transport replaces KEM. Single security layer (information-theoretic) + Ed25519 authentication. ~180x faster than V3 split-channel. Activated explicitly via
xchange: true.
V1, V2, V3 are the version progression. Xchange is a separate opt-in mode, not a version. Agents negotiate capabilities automatically. A v3 agent communicating with a v1 peer falls back to v1 envelope format. No developer action required — the protocol handles version negotiation internally.
postQuantumSig: true, all three cryptographic layers are quantum-safe. The payload is information-theoretically immune. Key exchange uses hybrid PQ KEM (FIPS 203). Authentication uses dual PQ signatures (FIPS 204). All 68 ACIs inherit this protection by updating one dependency.
Enterprise CLI
Self-hosted identity server. Docker-ready. Air-gapped capable. Port 3300. 73 tests. Part of the Enterprise CLI Suite.
@private.me/xlink-cli provides a production-grade identity management server with full REST API, agent lifecycle operations, trust registry management, and envelope processing. Integrates the complete @private.me/agent-sdk for enterprise M2M deployments.
CLI Commands
# Start the Xlink identity server xlink serve --port 3300 → HTTP server on :3300 with agent management + trust registry # Create a new agent identity xlink create --name "MyService" → Generates Ed25519 + X25519 keypairs, returns DID # Send an encrypted envelope xlink send --from "did:key:z6Mk..." --to "did:key:z6Mk..." --payload '{"action":"hello"}' → Creates v3 envelope, encrypts, signs, delivers # Receive and decrypt an envelope xlink receive --agent "did:key:z6Mk..." --envelope "base64-envelope" → Verifies signature, decrypts, validates nonce, returns payload # Register a DID in trust registry xlink register --did "did:key:z6Mk..." --name "Production API" --scope "api" → Adds to trust registry with scope-based permissions # Revoke a compromised agent xlink revoke --did "did:key:z6Mk..." --reason "key_compromise" → Marks DID as revoked, future envelopes rejected # Query trust registry xlink lookup --did "did:key:z6Mk..." → Returns registration status, scopes, metadata
Docker Deployment
# Pull and run the Xlink identity server docker compose up -d xlink # Verify health curl http://localhost:3300/health # {"status":"ok","version":"0.1.0","uptime":42} # Air-gapped deployment docker save private.me/xlink-cli > xlink-cli.tar # Transfer to air-gapped environment docker load < xlink-cli.tar docker compose up -d
Migration from API Keys
Zero-downtime migration. Gradual traffic shifting. Version negotiation. DualModeAdapter for API key + Xlink hybrid deployments.
Migrating from API key-based authentication to Xlink cryptographic identity is a gradual process. The SDK provides a DualModeAdapter that handles both legacy API key requests and modern Xlink envelope requests simultaneously. Traffic shifts progressively from keys to identity over weeks or months, with zero service interruption.
DualModeAdapter Overview
The DualModeAdapter sits at your service boundary and inspects incoming requests. If the request contains a traditional API key (Authorization header, query parameter, or custom header), it routes to your existing authentication logic. If the request contains a Xlink envelope (Content-Type: application/xlink-envelope), it verifies the signature, checks the nonce, and extracts the payload.
import { DualModeAdapter, Agent } from '@private.me/agent-sdk'; import express from 'express'; // Create Xlink agent for your service const agent = (await Agent.create({ name: 'ProductionAPI', registry, transport })).value!; // Initialize DualModeAdapter with legacy key validator const adapter = new DualModeAdapter({ agent, legacyKeyValidator: async (apiKey) => { // Your existing API key validation logic const user = await db.users.findOne({ apiKey }); return user ? { userId: user.id, scopes: user.scopes } : null; }, mode: 'hybrid', // Accept both keys and Xlink envelopes }); // Express middleware integration const app = express(); app.use(adapter.middleware()); app.post('/api/data', async (req, res) => { // req.auth contains either legacy user data OR Xlink sender DID if (req.auth.mode === 'xlink') { console.log(`Request from Xlink DID: ${req.auth.did}`); } else { console.log(`Legacy API key user: ${req.auth.userId}`); } res.json({ status: 'ok' }); });
req.auth.mode metrics.
Traffic Shifting Strategy
Migration proceeds in three phases: hybrid mode (both keys and Xlink accepted), deprecation warnings (keys still work but clients receive upgrade prompts), and enforcement (keys rejected, Xlink required). The DualModeAdapter mode parameter controls this progression.
| Phase | Mode Setting | API Key Requests | Xlink Requests | Duration |
|---|---|---|---|---|
| 1. Hybrid | hybrid |
Accepted | Accepted | 4-12 weeks |
| 2. Deprecation | deprecation |
Accepted + warning header | Accepted | 4-8 weeks |
| 3. Enforcement | xlink-only |
Rejected (401) | Accepted | Permanent |
// Week 0-12: Hybrid mode (both accepted) adapter.setMode('hybrid'); // Week 12-20: Deprecation warnings adapter.setMode('deprecation'); // API key requests receive X-Deprecated-Auth: "true" header // Response includes upgrade instructions in X-Migration-Url // Week 20+: Xlink enforcement adapter.setMode('xlink-only'); // API key requests return 401 with migration guide URL
Monitor Xlink adoption via adapter.getMetrics(). Once 95%+ of traffic uses Xlink (typically 8-12 weeks in hybrid mode), activate deprecation warnings. After another 4-8 weeks, enforce Xlink-only mode. Adjust timelines based on your client base and communication channels.
Version Negotiation
Xlink envelope versions (v1, v2, v3, Xchange) auto-negotiate based on sender and receiver capabilities. Agents inspect the recipient's registered keys in the trust registry and select the highest mutually supported version. No manual configuration required.
| Sender Capabilities | Receiver Capabilities | Negotiated Version | Security Features |
|---|---|---|---|
| v3 (Ed25519 + X25519 + ML-KEM + ML-DSA) | v3 (Ed25519 + X25519 + ML-KEM + ML-DSA) | v3 | Hybrid PQ KEM + dual signatures |
| v3 | v2 (Ed25519 + X25519 + ML-KEM, no ML-DSA) | v2 | Hybrid PQ KEM + Ed25519 sig only |
| v3 | v1 (Ed25519 + X25519, no PQ) | v1 | Classical crypto only |
| v3 + Xchange opt-in | v3 + Xchange opt-in | Xchange | IT-secure split, no KEM (faster) |
// Agent A: v3-capable (Ed25519 + X25519 + ML-KEM + ML-DSA) const agentA = (await Agent.create({ name: 'ServiceA', registry, transport, postQuantumSig: true, // Enables ML-DSA-65 })).value!; // Agent B: v2-capable (no ML-DSA support) const agentB = (await Agent.create({ name: 'ServiceB', registry, transport, postQuantumSig: false, // Only Ed25519 signatures })).value!; // When A sends to B, SDK auto-negotiates to v2 await agentA.send({ to: agentB.did, payload: data }); // Uses v2 envelope (ML-KEM-768 + Ed25519, no ML-DSA) // When A sends to another v3 agent, SDK uses v3 await agentA.send({ to: v3RecipientDid, payload: data }); // Uses v3 envelope (ML-KEM-768 + Ed25519 + ML-DSA-65)
Zero-Downtime Cutover Procedure
The recommended deployment pattern uses a phased rollout with canary testing and incremental traffic shifting. This procedure assumes a load-balanced multi-instance deployment behind a reverse proxy or API gateway.
Step 1: Canary Deployment (Week 0)
Deploy DualModeAdapter to a single instance (canary). Route 5% of traffic to it via load balancer weights. Monitor error rates, latency, and auth success metrics. If stable for 48 hours, proceed to Step 2.
# Load balancer config (example: nginx upstream weights) upstream api_backend { server instance1.internal:3000 weight=19; # Legacy (95%) server instance2.internal:3000 weight=1; # Canary DualMode (5%) }
Step 2: Gradual Rollout (Week 1-4)
Deploy DualModeAdapter to all instances. Shift traffic incrementally: 10% → 25% → 50% → 75% → 100% over 4 weeks. Monitor Xlink adoption rate. Target: 30-50% of clients using Xlink envelopes by end of Week 4.
# Week 1: 10% DualMode weight=18 (legacy), weight=2 (DualMode) # Week 2: 25% DualMode weight=15 (legacy), weight=5 (DualMode) # Week 3: 50% DualMode weight=10 (legacy), weight=10 (DualMode) # Week 4: 100% DualMode (all instances) all instances running DualModeAdapter in hybrid mode
Step 3: Client Migration (Week 5-12)
Publish Xlink integration guides and SDKs for your client ecosystem. Provide code examples, upgrade paths, and support channels. Track Xlink adoption via adapter.getMetrics(). Target: 80%+ adoption by end of Week 12.
Step 4: Deprecation Warnings (Week 13-20)
Switch DualModeAdapter to deprecation mode. API key requests still succeed but receive X-Deprecated-Auth: true header and migration guide URL. Monitor support tickets and client feedback. Extend timeline if needed.
Step 5: Enforcement (Week 21+)
Once 95%+ of traffic uses Xlink (typically Week 18-20), switch to xlink-only mode. API key requests return 401 Unauthorized with migration instructions. Maintain a support hotline for stragglers. After 4 weeks in enforcement mode, remove legacy API key validation code entirely.
hybrid mode or redeploying legacy instances. Keep legacy authentication logic intact until 4 weeks after enforcement begins. Have a tested rollback plan and practice it in staging.
Monitoring & Metrics
DualModeAdapter exposes real-time metrics for tracking migration progress and identifying adoption blockers.
const metrics = adapter.getMetrics(); console.log(metrics); // { // totalRequests: 142850, // xlinkRequests: 98420, // apiKeyRequests: 44430, // xlinkPercentage: 68.9, // failedAuth: 47, // avgLatencyMs: { xlink: 12.4, apiKey: 8.7 } // } // Export to monitoring system setInterval(() => { const m = adapter.getMetrics(); prometheus.gauge('xlink_adoption_pct').set(m.xlinkPercentage); prometheus.counter('xlink_requests_total').inc(m.xlinkRequests); }, 60000);
Track the xlinkPercentage metric daily. A healthy migration shows steady week-over-week growth: 10% → 25% → 45% → 65% → 85% → 95%. If growth stalls, investigate client-side integration blockers (SDK confusion, documentation gaps, support tickets).
Implementation Details
Deep-dive into error handling, trust registry, nonce store, transport adapters, and the complete ACI surface.
Error Hierarchy
Typed error classes for structured error handling. Supplementary to the Result<T,E> string code pattern.
XlinkError // Base class (code, subCode, docUrl) ├── XlinkIdentityError // Ed25519/X25519 keygen, sign, verify, DID ├── XlinkEnvelopeError // Envelope create, encrypt, decrypt, parse ├── XlinkTransportError // Send failures, network, timeouts ├── XlinkRegistryError // Lookup, registration, revocation ├── XlinkKeyAgreementError // X25519 ECDH derivation ├── XlinkSplitChannelError // XorIDA split, reconstruct, HMAC └── XlinkAgentError // High-level Agent lifecycle
import { toXlinkError, isXlinkError } from '@private.me/agent-sdk'; const result = await agent.receive(envelope); if (!result.ok) { const err = toXlinkError(result.error); console.log(err.code); // 'DECRYPT_FAILED' console.log(err.subCode); // 'KEY_AGREEMENT' console.log(err.docUrl); // 'https://xail.io/docs/packages/xlink#envelope' console.log(err.name); // 'XlinkEnvelopeError' }
Trust Registry
Four implementations covering development through production.
Production Persistence with FileTrustRegistry
For production single-node deployments, FileTrustRegistry provides persistent JSONL-based storage with automatic crash recovery. All operations append to an immutable log, replayed into memory on initialization.
import { Agent, FileTrustRegistry, HttpsTransportAdapter } from '@private.me/agent-sdk'; // JSONL file persists across restarts const registry = new FileTrustRegistry({ path: '/opt/app/trust.jsonl' }); const transport = new HttpsTransportAdapter({ baseUrl: 'https://api.example.com' }); const agent = (await Agent.create({ name: 'ProductionService', registry, transport })).value!; // Registry automatically persists all add/update/remove operations // Survives process restarts, crashes, and power failures
Enterprise Multi-Backend Factory
For enterprise deployments requiring centralized trust management with local fallback, createEnterpriseTrustRegistry() provides a factory function that combines HTTP primary + File fallback in a single interface.
import { createEnterpriseTrustRegistry } from '@private.me/agent-sdk'; // Primary: centralized HTTP registry // Fallback: local JSONL file when HTTP unreachable const registry = createEnterpriseTrustRegistry({ http: { baseUrl: 'https://trust.corp.example.com', authToken: process.env.TRUST_TOKEN }, file: { path: '/var/lib/trust-fallback.jsonl' } }); // Reads try HTTP first, fall back to file on network failure // Writes go to both backends for redundancy
Nonce Store & Anti-Replay Protection
Replay prevention via unique nonce tracking. Every envelope carries a cryptographically random 16-byte nonce generated via crypto.getRandomValues().
Nonce-Based Replay Attack Prevention
A nonce (number used once) is a cryptographic value that ties each envelope to a single use. When an envelope arrives, the receiver checks whether its nonce has been seen before. If the nonce exists in the store, the envelope is rejected with REPLAY_DETECTED — preventing an attacker from capturing a valid envelope and re-sending it to execute the same action twice. This pattern is based on established cryptographic nonce practices used in OAuth 2.0, OIDC, and blockchain protocols.
| Step | Actor | Action | Defense |
|---|---|---|---|
| 1. Generate | Sender | 16-byte nonce via crypto.getRandomValues() | High-entropy random |
| 2. Include | Sender | Nonce embedded in envelope (signed) | Integrity-protected |
| 3. Check | Receiver | NonceStore.check(nonce, senderDid) | Atomic set-if-not-exists |
| 4. Store | Receiver | Nonce stored with TTL expiry | Time-bound memory usage |
| 5. Replay | Attacker | Re-sends captured envelope | Rejected (duplicate nonce) |
Implementation Options
import { RedisNonceStore } from '@private.me/agent-sdk'; import Redis from 'ioredis'; const redis = new Redis({ host: 'redis.example.com' }); const nonceStore = new RedisNonceStore({ client: redis, ttlSeconds: 600, // 10 minutes (default) keyPrefix: 'nonce:', // Redis key namespace }); const agent = await Agent.create({ name: 'DistributedAgent', registry, transport, nonceStore, // Cross-node protection });
Production deployments should use RedisNonceStore or an equivalent distributed store. Memory-based stores clear on process restart, allowing replay attacks within the timestamp window until the TTL expires. Redis provides atomic SET NX EX semantics, ensuring that even under high concurrency, a nonce is either accepted once or rejected as a duplicate — no race conditions.
Transport Adapters
Pluggable delivery mechanism. Two built-in adapters plus a custom interface.
interface XailTransportAdapter { send(envelope: TransportEnvelope, to: string): Promise<Result<void, TransportError>>; onReceive(handler: (envelope: TransportEnvelope) => void): void; dispose(): void; }
Full ACI Surface
Complete Authenticated Cryptographic Interface organized by module.
Agent
Generate identity, register with registry, wire transport. Primary factory.
Restore from persisted PKCS8 identity. Skips keygen + registration.
Synchronous construction from pre-built components. No async, no Result.
Deterministic identity from 32-byte seed via HKDF-SHA256. IoT factory provisioning.
Encrypt, sign, and deliver. Auto ECDH when available. Automatic split-channel for high-risk operations.
Verify version, timestamp, nonce, sender, signature, scope. Decrypt. Parse.
Identity
Ed25519 + X25519 keypair generation via Web Crypto API. ML-DSA-65 keys when postQuantumSig: true.
PKCS8 DER export/import for identity persistence.
HKDF-SHA256 deterministic derivation from 32-byte seed.
Envelope
Encrypted or signed-only envelope creation.
Validation and deserialization from unknown input.
Split-Channel
XorIDA split with HMAC integrity. Default: 3 shares, threshold 2.
Reconstruct from K+ shares. HMAC verified before returning plaintext.
Key Agreement
Hybrid X25519 + ML-KEM-768 key agreement with ephemeral key pair per message.
Error Taxonomy
Complete error code table with sub-codes and error classes.
| Code | Class | When |
|---|---|---|
| IDENTITY_FAILED | Agent | Identity generation fails during create() |
| REGISTRATION_FAILED | Agent | Registry rejects registration |
| REGISTRATION_FAILED:ALREADY_REGISTERED | Agent | DID already exists in registry |
| VERIFICATION_FAILED:SIGNATURE_MISMATCH | Agent | Ed25519 signature does not match |
| VERIFICATION_FAILED:DID_NOT_IN_REGISTRY | Agent | Sender DID not found in registry |
| REPLAY_DETECTED | Agent | Duplicate nonce (replay attack) |
| TIMESTAMP_EXPIRED | Agent | Envelope outside timestamp window |
| SCOPE_DENIED | Agent | Sender lacks required scope |
| DECRYPT_FAILED:KEY_AGREEMENT | Envelope | ECDH derivation fails |
| DECRYPT_FAILED:DECRYPTION | Envelope | AES-GCM decryption fails |
| DECRYPT_FAILED:PARSE | Envelope | Decrypted bytes not valid JSON |
| SEND_FAILED:BELOW_THRESHOLD | Transport | Split: fewer than K shares delivered |
| HMAC_VERIFICATION_FAILED | SplitChannel | Share HMAC check fails |
| INSUFFICIENT_SHARES | SplitChannel | Fewer than threshold shares |
| INCONSISTENT_SHARES | SplitChannel | Mismatched groupId or params |
| INVALID_KEY_LENGTH:EXPECTED_32 | KeyAgreement | X25519 key not 32 bytes |
Codebase Stats
Xlink v0.1.0 — Gold Standard Bronze tier achieved.
Module Inventory
| Module | Source File | Purpose |
|---|---|---|
| Identity | identity.ts | Ed25519 + X25519 keygen, DID, PKCS8, sign/verify |
| Envelope | envelope.ts | v1 create/decrypt/serialize/validate |
| Agent | agent.ts | Top-level ACI (create, send, receive, middleware) |
| Split-Channel | split-channel.ts | XorIDA bridge: split, reconstruct, HMAC |
| Key Agreement | key-agreement.ts | X25519 ECDH ephemeral key agreement |
| Nonce Store | nonce-store.ts | MemoryNonceStore for replay prevention |
| Redis Nonce | redis-nonce-store.ts | RedisNonceStore for distributed deployments |
| Trust Registry | trust-registry.ts | Memory + HTTP registry |
| DID:web | did-web.ts | W3C did:web resolver |
| Transport | transport.ts | Interface + HTTPS adapter |
| Gateway | gateway-transport.ts | Xail inbox gateway delivery |
| Middleware | registry-middleware.ts | Express auth middleware for registry |
| Errors | errors.ts | Error class hierarchy (XlinkError + 7) |
| Verify | verify.ts | Lightweight verify-only sub-path |
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/xlink- 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