Xark: Physical Identity Backup
Split cryptographic identity keys (Ed25519 signing + X25519 encryption) into K-of-N XorIDA shares encoded as Base45 QR codes for offline physical storage. Print to paper, laminate onto cards, or store in safe deposit boxes. Reconstruction requires physical possession of K shares. Information-theoretically secure — fewer than K shares reveal zero information, unconditionally.
Executive Summary
Xark solves the cryptographic identity recovery problem: how do you back up private keys without creating a single point of compromise?
Two functions cover the complete lifecycle: splitIdentity() takes Ed25519 + X25519 key pairs and produces N QR-printable shares. Any K of those shares can reconstruct the original keys via reconstructIdentity(). Each share is a Base45-encoded string optimized for QR code alphanumeric mode (smallest possible QR codes). The DID is printed in cleartext alongside each QR for human identification.
Physical distribution is the security model. Print three shares, laminate them onto credit-card-sized cards, and distribute: one in your safe deposit box, one with your lawyer, one with a trusted family member. Loss or theft of any single share reveals zero information about your private keys — not computationally hard to break, but mathematically impossible. Recovery requires presenting any two of the three physical cards and scanning their QR codes.
Zero external dependencies. Runs anywhere Web Crypto API is available. Built on XorIDA threshold sharing (information-theoretic security over GF(2)), HMAC-SHA256 integrity verification, PKCS7 padding, Xformat envelope serialization, and RFC 9285 Base45 encoding.
Developer Experience
Xark provides structured error codes and detailed validation to help developers build reliable identity backup workflows.
Quick Start
import { splitIdentity, reconstructIdentity } from '@private.me/identityark'; // Your cryptographic identity keys const keys = { signingKey: ed25519Seed, // 32 bytes encryptionKey: x25519Private, // 32 bytes did: 'did:key:z6Mk...', label: 'Primary Agent', }; // Split into 2-of-3 shares const result = await splitIdentity(keys, { n: 3, k: 2 }); if (result.ok) { for (const share of result.value.shares) { renderQrCode(share.qrData); // Base45 string -> QR console.log(share.did); // Cleartext DID console.log(share.shareId); // 1, 2, or 3 } } // Reconstruct from any 2 of the 3 shares const restored = await reconstructIdentity([ shares[0].qrData, shares[2].qrData, ]); if (restored.ok) { console.log(restored.value.did); // Original DID console.log(restored.value.signingKey); // Uint8Array(32) console.log(restored.value.encryptionKey); // Uint8Array(32) }
Structured Error Handling
Xark uses a Result<T, E> pattern with hierarchical error codes. Sub-codes use colon-separated namespacing for precise error attribution.
const result = await splitIdentity(keys, config); if (!result.ok) { switch (result.error) { case 'INVALID_KEYS:SIGNING_KEY_LENGTH': console.error('Signing key must be exactly 32 bytes'); break; case 'INVALID_CONFIG:K_EXCEEDS_N': console.error('Threshold k cannot exceed total shares n'); break; case 'RECONSTRUCT_FAILED:HMAC_MISMATCH': console.error('Shares corrupted or tampered'); break; } }
Error Categories
| Category | Example Codes | When |
|---|---|---|
| Configuration | INVALID_CONFIG:N_TOO_SMALL, K_EXCEEDS_N | n or k validation fails |
| Key Validation | INVALID_KEYS:SIGNING_KEY_LENGTH, MISSING_DID | Key material validation |
| Split Operations | SPLIT_FAILED, HMAC_FAILED | XorIDA split or HMAC generation |
| Reconstruction | HMAC_MISMATCH, UNPAD, DESERIALIZE | Share reconstruction, verification |
| Share Consistency | UUID_MISMATCH, DUPLICATE_SHARE | Cross-share validation |
The Problem
Cryptographic identities are single points of failure. Lose your private key and you lose access forever. Traditional backup methods create new vulnerabilities.
Existing Approaches Fail
| Approach | Single Point of Failure | Risk |
|---|---|---|
| Hardware wallet | ✗ Device loss/damage | Permanent key loss |
| 24-word seed phrase | ✗ Paper discovery | Complete compromise |
| Encrypted backup file | ✗ Password forgotten | Unrecoverable |
| Cloud key storage | ✗ Provider breach | Mass credential theft |
| K-of-N threshold backup | ✓ No single point | Requires K coordinated thefts |
What Xark Solves
1. Eliminates single points of failure. Any K-1 shares reveal zero information. Losing one share does not compromise security. Finding one share gives an attacker nothing.
2. Physical distribution = security boundary. Your safe deposit box, your lawyer's office, your family member's home are independent security domains. Coordinated compromise is exponentially harder than digital breach.
3. Human-readable identification. Each share prints the DID in cleartext. You know which identity the share belongs to without scanning the QR.
4. Fault tolerance. With 2-of-3, you can lose one share permanently and still recover. With 3-of-5, you can lose two shares. Threshold configuration is flexible (2-255 shares).
5. No trusted third party. No key escrow service, no custodian, no server. The split IS the backup. Reconstruction is entirely client-side.
Use Cases
Four primary deployment scenarios for physical cryptographic identity backup.
Split root signing keys for enterprise agents into 3-of-5 shares. Distribute across: CEO safe deposit box, CFO office safe, legal counsel vault, board member secure storage, offshore backup location. Any three executives can reconstruct to authorize critical operations. Prevents single-person key control.
SOX complianceLaw firms split cryptographic signing keys used for privileged communications. 2-of-3 distribution: attorney's office, client's safe, court-approved escrow. Loss of one location (fire, theft) does not compromise privilege. Recovery requires client consent (physical presentation of card).
Ethical wallsHospital splits medical record signing keys. 2-of-3 shares across: Chief Medical Officer, Hospital Administrator, Department of Health escrow. Prevents single-administrator key compromise. Reconstruction requires two independent authorities for audit log signing.
HIPAA 164.312(a)(2)(i)Investment firms split algorithmic trading system identity keys. 3-of-5 across: trading floor safe, compliance vault, CEO office, offshore backup, regulator escrow. Daily operations use ephemeral keys derived from reconstructed master. Root key reconstruction requires quorum.
SEC 17a-4Defense agencies split agent identity keys for classified network access. 2-of-3 distribution across: facility SCIF, regional command, CONUS backup. Physical separation enforces need-to-know. Reconstruction requires presenting two physical tokens at secure terminal.
NIST SP 800-57Personal agent identity backup. 2-of-3 shares: your safe deposit box, trusted family member, lawyer's vault. If your device is lost or stolen, scan any two QR codes to reconstruct your cryptographic identity on a new device. DID remains constant across recovery.
Estate planningArchitecture
Seven-stage cryptographic pipeline: serialize, pad, HMAC, split, envelope, encode, print.
Split Pipeline
The splitIdentity() function transforms 64 bytes of key material (Ed25519 signing key + X25519 encryption key) into N QR-printable shares:
// 1. Serialize identity keys to binary [signingKey: 32B][encryptionKey: 32B][didLen: 2B BE][did: UTF-8][labelLen: 2B][label: UTF-8] // 2. PKCS#7 pad to XorIDA block boundary blockSize = nextOddPrime(n) - 1 padded = pkcs7Pad(serialized, blockSize) // 3. Generate HMAC-SHA256 for integrity { key: hmacKey, signature: hmacSig } = generateHMAC(padded) // 4. XorIDA split into n shares shares = splitXorIDA(padded, n, k) // GF(2) threshold sharing // 5. Wrap each share in Xformat envelope envelope = { productType: XARK, n, k, shareId, uuid: generateUUID(), payload: concat(hmacKey, hmacSig, share) } // 6. Base45 encode for QR alphanumeric mode qrData = encodeBase45(serializeEnvelope(envelope)) // 7. Return with cleartext DID for identification return { shareId, qrData, did, label, n, k }
Reconstruct Pipeline
The reconstructIdentity() function reverses the split pipeline:
// 1. Decode Base45 QR strings decoded = qrShares.map(qr => decodeBase45(qr)) // 2. Deserialize Xformat envelopes envelopes = decoded.map(deserializeEnvelope) // 3. Consistency checks assert all envelopes have same UUID assert no duplicate share IDs assert envelopes.length >= k // 4. Extract HMAC + share data from payloads hmacKey = envelopes[0].payload.subarray(0, 32) hmacSig = envelopes[0].payload.subarray(32, 64) shareData = envelopes.map(e => e.payload.subarray(64)) // 5. XorIDA reconstruct from k shares reconstructed = reconstructXorIDA(shareData, shareIndices, n, k) // 6. HMAC verification (BEFORE unpadding — fail closed) if (!verifyHMAC(hmacKey, reconstructed, hmacSig)) { return err('RECONSTRUCT_FAILED:HMAC_MISMATCH') } // 7. Unpad and deserialize unpadded = pkcs7Unpad(reconstructed, blockSize) keys = deserializeIdentity(unpadded) // Extract signing + encryption keys
QR Encoding Strategy
Base45 (RFC 9285) encoding is optimized for QR code alphanumeric mode. QR alphanumeric supports 45 characters (0-9, A-Z, space, and 9 symbols). Base45 uses exactly this character set, producing the smallest possible QR codes for binary data. Base64 would force QR byte mode (8 bits/char vs 5.5 bits/char for alphanumeric), increasing QR code size by ~45%.
| Encoding | QR Mode | Bits/Char | Size for 64B Keys |
|---|---|---|---|
| Base64 | Byte | 8 | ~29x29 modules |
| Base45 | Alphanumeric | 5.5 | ~25x25 modules |
| Hex | Alphanumeric | 5.5 | ~27x27 modules |
For a 2-of-3 split of Ed25519+X25519 keys (64 bytes raw), Base45-encoded Xark shares produce Version 3 QR codes (29x29 modules) at error correction level M. These fit comfortably on credit-card-sized laminated cards (85.6mm x 53.98mm).
Integration Patterns
Four deployment scenarios for identity backup workflows.
Wallet App Recovery Flow
import { Agent } from '@private.me/agent-sdk'; import { splitIdentity } from '@private.me/identityark'; // User creates new wallet identity const agent = await Agent.create({ name: 'MyWallet', registry, transport }); // Export keys for backup const { signingKeySeed, encryptionKeySeed } = await agent.exportSeeds(); // Split into 2-of-3 shares const result = await splitIdentity( { signingKey: signingKeySeed, encryptionKey: encryptionKeySeed, did: agent.did, label: 'MyWallet Primary', }, { n: 3, k: 2 } ); // Render QR codes for user to print/save for (const share of result.value.shares) { displayQrCode(share.qrData, share.shareId, share.did); }
Enterprise Key Ceremony
// Air-gapped laptop generates root identity const rootAgent = await Agent.create({ name: 'CorporateRootCA', registry: offlineRegistry, transport: nullTransport, }); const { signingKeySeed, encryptionKeySeed } = await rootAgent.exportSeeds(); // Split into 3-of-5 for executive distribution const ceremony = await splitIdentity( { signingKey: signingKeySeed, encryptionKey: encryptionKeySeed, did: rootAgent.did }, { n: 5, k: 3 } ); // Print shares on tamper-evident paper for (const share of ceremony.value.shares) { printToSecurePrinter(share.qrData, share.shareId, share.did); } // Witnesses sign distribution log recordKeyCeremony({ splitId: ceremony.value.splitId, timestamp: new Date(), witnesses: [ceo, cfo, cto], });
Personal Recovery Workflow
// Lost phone scenario — user scans 2 of 3 QR codes const qr1 = await scanQrCode(); // From safe deposit box card const qr2 = await scanQrCode(); // From lawyer's vault card const restored = await reconstructIdentity([qr1, qr2]); if (restored.ok) { // Restore agent identity on new device const agent = await Agent.fromSeeds({ signingKeySeed: restored.value.signingKey, encryptionKeySeed: restored.value.encryptionKey, did: restored.value.did, }); // Re-register with trust registry await registry.register(agent.did, agent.publicKeys); console.log('Identity restored on new device', agent.did); }
Automated Backup on First Use
// Wallet prompts user to create backup on first launch async function onboardingBackup(agent: Agent) { const { signingKeySeed, encryptionKeySeed } = await agent.exportSeeds(); const split = await splitIdentity( { signingKey: signingKeySeed, encryptionKey: encryptionKeySeed, did: agent.did }, { n: 3, k: 2 } ); if (!split.ok) throw new Error(split.error); // Show all three QR codes on screen with instructions showBackupModal({ shares: split.value.shares, instructions: [ 'Screenshot and print this page', 'Cut along the lines to create 3 cards', 'Laminate each card', 'Store in 3 separate physical locations', 'You need any 2 cards to recover your wallet', ], }); }
Security
Information-theoretic security, HMAC verification before use, constant-time comparisons, fail-closed reconstruction.
Information-Theoretic Guarantee
XorIDA operates over GF(2) (binary field). Any K-1 or fewer shares reveal exactly zero information about the original keys. This is not computational security (breakable with enough computing power) — it is information-theoretic security (mathematically impossible to break regardless of computing power, including quantum computers).
HMAC Verification Before Use
Reconstructed data is HMAC-SHA256 verified BEFORE unpadding or deserialization. If HMAC fails, the result is rejected. No partial data is returned. This ensures fail-closed behavior: corrupted or tampered shares never produce plaintext.
// Step 1: XorIDA reconstruct (may produce garbage if shares corrupted) const reconstructed = reconstructXorIDA(shareData, shareIndices, n, k); // Step 2: HMAC verification BEFORE unpadding (fail closed) const hmacValid = await verifyHMAC(hmacKey, reconstructed, hmacSig); if (!hmacValid) { return err('RECONSTRUCT_FAILED:HMAC_MISMATCH'); } // Step 3: Only if HMAC passes → unpad and deserialize const unpadded = pkcs7Unpad(reconstructed, blockSize); const keys = deserializeIdentity(unpadded);
UUID Consistency
All shares from the same split operation carry the same UUID in their Xformat envelopes. During reconstruction, UUID consistency is verified using constant-time byte comparison to prevent timing side channels. If shares belong to different split operations, reconstruction fails with UUID_MISMATCH.
Threat Model
| Threat | Mitigation |
|---|---|
| Single-location compromise | K-1 shares reveal zero information (IT-secure) |
| QR code tampering | HMAC verification rejects modified shares |
| Share swapping attack | UUID mismatch detection, share ID uniqueness check |
| Timing side channels | Constant-time UUID comparison |
| Physical QR wear/damage | QR error correction (Level M = 15% damage tolerance) |
| K-location coordinated theft | Not mitigated — physical security boundary |
What Xark Does NOT Protect
Benchmarks
Performance characteristics for identity backup operations. Measured on MacBook Pro M1 (8-core, 16GB RAM).
Split Operations
| Configuration | Time (avg) | QR Size | Operations |
|---|---|---|---|
| 2-of-2 | <1ms | ~25x25 modules | Serialize, pad, HMAC, split, envelope, Base45 |
| 2-of-3 | <1ms | ~25x25 modules | Same as above |
| 3-of-5 | <2ms | ~29x29 modules | Same as above |
| 5-of-9 | <3ms | ~29x29 modules | Same as above |
Reconstruct Operations
| Configuration | Time (avg) | Operations |
|---|---|---|
| 2-of-2 | <1ms | Base45 decode, envelope parse, XorIDA, HMAC verify, unpad, deserialize |
| 2-of-3 | <1ms | Same as above |
| 3-of-5 | <2ms | Same as above |
| 5-of-9 | <3ms | Same as above |
QR Code Size Comparison
For Ed25519 + X25519 keys (64 bytes raw), Xark produces compact QR codes suitable for credit-card-sized lamination:
| Share Config | Payload Size | Base45 Length | QR Version | QR Size |
|---|---|---|---|---|
| 2-of-2 | ~64B keys + envelope overhead | ~120 chars | V2 | 25x25 modules |
| 2-of-3 | ~64B keys + envelope overhead | ~120 chars | V2 | 25x25 modules |
| 3-of-5 | ~64B keys + envelope overhead | ~140 chars | V3 | 29x29 modules |
Honest Limitations
What Xark does not solve, by design or by scope.
Physical Security Assumptions
Xark assumes you control physical distribution. If an attacker gains access to K or more share locations, your keys are compromised. Xark cannot protect against:
- Coordinated multi-location theft (burglar steals your safe deposit box AND your lawyer's vault)
- Coercion (forcing you to present K shares under duress)
- Insider access (lawyer's paralegal + your spouse both have share access)
- Government seizure (warrant for K locations simultaneously)
Mitigation: Choose geographically separated, independent locations with different security domains (bank vault, lawyer's office, trusted family member in different state, offshore safe deposit box).
No Key Rotation
Once split, keys are static. Xark does not support key rotation without re-splitting. If you rotate your agent identity keys (e.g., after suspected compromise), you must:
- Generate new keys
- Split the new keys into new shares
- Physically replace all old QR cards with new QR cards
- Securely destroy old QR cards
This is intentional: physical distribution IS the security model. You cannot remotely revoke a printed QR card.
QR Code Damage Tolerance
Error correction level M = 15% damage tolerance. QR codes can recover from minor physical damage (scratches, small stains, folds). Beyond 15% damage, the QR becomes unreadable. Lamination helps, but it's not bulletproof.
Mitigation: Use higher error correction (Level Q = 25%, Level H = 30%) for harsh environments, at the cost of larger QR codes. Xark currently defaults to Level M for size optimization.
No Multi-Party Computation
Reconstruction requires all K shares to be scanned by a single device. Xark does not support distributed reconstruction where K parties each contribute their share without revealing it to others. For distributed key ceremonies, see Xfuse (threshold identity fusion with MPC convergence).
Fixed Key Types
Xark only backs up Ed25519 + X25519 keys. It does not support:
- Arbitrary key types (RSA, ECDSA, ML-DSA)
- Multi-key bundles (more than 2 keys)
- Hierarchical deterministic key derivation paths (BIP32/BIP44)
Rationale: Xark is designed for PRIVATE.ME agent identity backup. If you need arbitrary key backup, use the underlying XorIDA + Xformat primitives directly.
No Share Refresh
Shares are immutable. Some threshold schemes support proactive secret sharing (refreshing shares without changing the secret). Xark does not. Once shares are printed and distributed, they remain static until you rotate the underlying keys.
Implication: If you want to change the threshold (e.g., 2-of-3 to 3-of-5), you must reconstruct the original keys, split again with the new configuration, and replace all physical cards.
Advanced Topics
Deep dives into API surface, error taxonomy, and Xformat envelope structure for library integrators and security auditors.
Full API Surface
Complete type definitions and function signatures.
Public Functions
Split identity keys into K-of-N QR-printable shares. Each share is a Base45-encoded Xformat envelope containing one XorIDA share of the serialized identity. The DID is included in cleartext alongside each share for human identification.
Reconstruct identity keys from K or more QR shares. HMAC verification happens before unpadding (fail-closed). Returns original signing key, encryption key, DID, and optional label.
Type Definitions
interface IdentityKeys { readonly signingKey: Uint8Array; // Ed25519 seed (32B) readonly encryptionKey: Uint8Array; // X25519 private (32B) readonly did: string; // DID:key identifier readonly label?: string; // Optional label } interface ArkConfig { readonly n: number; // Total shares (2-255) readonly k: number; // Threshold (2-n) } interface IdentityShare { readonly shareId: number; // 1-based index readonly qrData: string; // Base45 QR string readonly did: string; // Cleartext DID readonly label?: string; // Optional label readonly n: number; // Total shares readonly k: number; // Threshold } interface ArkSplitResult { readonly shares: ReadonlyArray<IdentityShare>; readonly splitId: string; // UUID readonly did: string; // Backed-up DID } interface ArkReconstructResult { readonly signingKey: Uint8Array; readonly encryptionKey: Uint8Array; readonly did: string; readonly label?: string; }
Error Taxonomy
Complete error code reference with hierarchical sub-codes.
Error Code Structure
Xark uses colon-separated error codes for hierarchical categorization. Base codes (e.g., INVALID_CONFIG) are kept for backward compatibility. Sub-codes (e.g., INVALID_CONFIG:K_EXCEEDS_N) provide precise attribution.
| Code | Category | Meaning |
|---|---|---|
| INVALID_CONFIG | Config | Generic configuration error |
| INVALID_CONFIG:N_TOO_SMALL | Config | n < 2 (minimum 2 shares required) |
| INVALID_CONFIG:K_TOO_SMALL | Config | k < 2 (minimum threshold is 2) |
| INVALID_CONFIG:K_EXCEEDS_N | Config | k > n (threshold cannot exceed total shares) |
| INVALID_KEYS | Validation | Generic key validation error |
| INVALID_KEYS:SIGNING_KEY_LENGTH | Validation | Signing key is not exactly 32 bytes |
| INVALID_KEYS:ENCRYPTION_KEY_LENGTH | Validation | Encryption key is not exactly 32 bytes |
| INVALID_KEYS:MISSING_DID | Validation | DID is empty or missing |
| SPLIT_FAILED | Operation | XorIDA split or envelope serialization failed |
| HMAC_FAILED | Crypto | HMAC generation failed (Web Crypto unavailable) |
| RECONSTRUCT_FAILED | Operation | Generic reconstruction error |
| RECONSTRUCT_FAILED:HMAC_MISMATCH | Security | HMAC verification failed (data corrupted or tampered) |
| RECONSTRUCT_FAILED:UNPAD | Crypto | PKCS#7 unpadding failed (invalid padding bytes) |
| RECONSTRUCT_FAILED:DESERIALIZE | Parsing | Binary deserialization of identity keys failed |
| RECONSTRUCT_FAILED:INVALID_ENVELOPE | Parsing | Base45 decoding or Xformat envelope parsing failed |
| RECONSTRUCT_FAILED:PRODUCT_MISMATCH | Validation | Envelope product type is not XARK |
| RECONSTRUCT_FAILED:UUID_MISMATCH | Consistency | Shares belong to different split operations |
| INSUFFICIENT_SHARES | Validation | Fewer than k shares provided |
| DUPLICATE_SHARE | Validation | Two or more shares have the same shareId |
Xformat Envelope
Binary envelope structure for Xark shares.
Envelope Layout
Each Xark share is wrapped in an Xformat envelope before Base45 encoding. The envelope provides product identification, share metadata, and payload framing:
// Magic number (4 bytes) 0x49444135 // "IDA5" in ASCII // Product type (1 byte) 0x0C // XARK product code // Share metadata (3 bytes) n: uint8 // Total shares k: uint8 // Threshold shareId: uint8 // 1-based share index // UUID (16 bytes) uuid[16] // Split operation identifier // Payload (variable length) hmacKey[32] // HMAC-SHA256 key hmacSig[32] // HMAC-SHA256 signature shareData[*] // XorIDA share (variable)
Product Type Registry
Xark uses product type 0x0C in the Xformat registry. Other PRIVATE.ME products use different codes:
| Code | Product | Purpose |
|---|---|---|
| 0x01 | Xmail | Split-channel email shares |
| 0x02 | Xecret | Seed custody shares |
| 0x05 | Xstore | Universal split-storage shares |
| 0x0C | Xark | Identity backup shares |
| 0x0F | Xlink | M2M split-channel shares |
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
- Enterprise SLA available
SDK Integration
Embed directly in your application. Runs in your codebase with full programmatic control.
npm install @private.me/identityark- TypeScript/JavaScript SDK
- Full source access
- Enterprise support available
On-Premise Upon Request
Enterprise CLI for compliance, air-gap, or data residency requirements.
- Complete data sovereignty
- Air-gap capable deployment
- Custom SLA + dedicated support
- Professional services included
Enterprise On-Premise Deployment
While identityArk is primarily delivered as SaaS or SDK, we build dedicated on-premise infrastructure for customers with:
- Regulatory mandates — HIPAA, SOX, FedRAMP, CMMC requiring self-hosted processing
- Air-gapped environments — SCIF, classified networks, offline operations
- Data residency requirements — EU GDPR, China data laws, government mandates
- Custom integration needs — Embed in proprietary platforms, specialized workflows
Includes: Enterprise CLI, Docker/Kubernetes orchestration, RBAC, audit logging, and dedicated support.