Trust: Key Verification & Transparency
Key verification, TOFU trust management, key rotation with continuity proofs, and append-only transparency logs. Trust implements the full trust lifecycle for contact public keys: Trust-on-First-Use for initial acceptance, mutual verification via QR codes and Short Authentication Strings, RSA-PSS continuity proofs for rotation, and hash-chained tamper-evident key history. All operations use the Web Crypto API.
Executive Summary
Trust solves the critical problem of key management in end-to-end encrypted messaging: how to verify that a contact's public key is authentic and detect when keys change unexpectedly.
Traditional messaging systems fail in two ways. They either trust keys blindly on first use without verification (vulnerable to MITM attacks), or they require out-of-band verification for every contact (poor UX that most users skip). Trust implements a graduated approach: Trust-on-First-Use (TOFU) for initial key acceptance with automatic warnings on unexpected changes, mutual verification ceremonies using QR codes and Short Authentication Strings to upgrade contacts to "verified" status, and cryptographic continuity proofs for authorized key rotations.
An append-only transparency log with SHA-256 hash chaining creates a tamper-evident audit trail. Every key event — registration, rotation, verification, revocation — is logged with a cryptographic link to its predecessor. Log corruption is detectable. Key rotation uses RSA-PSS signatures: the old private key signs the new public key, proving authorization. A configurable grace period (default: 7 days) allows both keys during transition.
Zero configuration out of the box. Zero npm runtime dependencies. Runs anywhere the Web Crypto API is available. Dual ESM and CJS builds ship in a single package.
The Problem
Public key cryptography solves confidentiality but introduces a new problem: how do you know this key belongs to the person you think it does?
The Key Distribution Problem
Alice wants to send an encrypted message to Bob. She needs Bob's public key. But where does she get it? If an attacker Mallory intercepts the key exchange, Mallory can substitute their own public key. Alice encrypts to Mallory's key, thinking it's Bob's. The encryption is perfect — but the conversation is compromised from the start. Encryption protects the channel. It doesn't authenticate the endpoints.
Current Approaches Fail
Most messaging apps use Trust-on-First-Use (TOFU): blindly accept the first key seen for each contact. This works until keys change. When Bob rotates his key, Alice has no way to know if the new key is legitimately Bob's or an attacker's. Signal displays a "safety number" for verification, but studies show fewer than 1% of users verify. The UX friction is too high.
Certificate Authorities (CAs) solve this for HTTPS but require centralized trust hierarchies. If a CA is compromised or coerced, the entire trust model collapses. CA-based PKI doesn't work for peer-to-peer messaging where no central authority exists.
What's Missing
There is no standard implementation that combines TOFU's ease of use with opt-in verification for high-value contacts, cryptographic continuity proofs for key rotation, and a tamper-evident transparency log. Trust fills this gap. It implements the full trust lifecycle as a reusable building block.
Use Cases
Trust is a building block for any application that exchanges cryptographic keys. Primary markets: secure messaging, email clients, M2M communication, enterprise key management.
Email clients implementing end-to-end encryption need to manage recipient public keys. Trust provides TOFU for initial key exchange, verification ceremonies for contacts you meet in person, and automatic rotation handling.
PGP replacementOrganizations need to manage employee cryptographic identities at scale. Trust's transparency log provides an auditable record of all key events. Continuity proofs prevent unauthorized key replacement.
Zero TrustIoT devices and microservices exchange signed messages. Trust manages device key lifecycles, detects unexpected key changes (indicating device compromise), and logs all rotation events.
ACI identityLegal and healthcare communications require cryptographic proof of authenticity. Trust's hash-chained transparency log provides tamper-evident audit trails for compliance and forensic investigation.
HIPAA / Attorney-ClientFinancial institutions need cryptographic authentication for trading counterparties. Trust's verification ceremonies establish high-assurance identity. Rotation grace periods ensure uninterrupted trading during key transitions.
SEC 17a-4Government agencies exchange classified information across security domains. Trust's transparency log provides cryptographic proof of key lineage. Continuity proofs prevent unauthorized key substitution.
FISMA / CMMCArchitecture
Trust is a client-side TypeScript library with four core components: TofuManager, KeyTransparencyLog, Verification Ceremony, and KeyRotationManager.
Trust-on-First-Use (TOFU)
When a user first sees a contact's key, TofuManager.processKey() accepts it automatically and assigns "tofu" trust level. The key fingerprint is stored. On subsequent messages, if the fingerprint matches, communication proceeds normally. If the fingerprint changes, the system warns the user — potential MITM attack or legitimate rotation. The user must explicitly call acceptKeyChange() to accept the new key. This prevents silent key replacement.
const log = new KeyTransparencyLog(); const tofu = new TofuManager(log); // First time seeing alice@example.com const fingerprint = await computeFingerprint(alicePublicKey); const result = await tofu.processKey('alice@example.com', fingerprint); // => { trustLevel: 'tofu', fingerprint: '...', firstSeenAt: ... } // Later: Alice sends a message from a new key const newFingerprint = await computeFingerprint(aliceNewKey); const result2 = await tofu.processKey('alice@example.com', newFingerprint); // => { ok: false, error: { code: 'FINGERPRINT_CONFLICT', ... } } // User confirms it's legitimate (Alice rotated her key) await tofu.acceptKeyChange('alice@example.com', newFingerprint); // => { ok: true, value: { trustLevel: 'tofu', ... } }
Verification Ceremony
To upgrade from "tofu" to "verified" trust, both parties perform a mutual verification ceremony. createChallenge() generates a QR code and a 6-word Short Authentication String (SAS) derived from both fingerprints via HMAC-SHA256. Both parties compare the SAS phrase (e.g., "amber falcon cedar lotus bridge crimson"). If they match, the keys are authentic. If they don't, there's a MITM attack. After successful verification, upgradeTrust() marks the contact as "verified".
// Initiator creates a challenge const challenge = await createChallenge( 'me@private.me.io', 'alice@example.com', myPublicKey, alicePublicKey ); // Display QR code and SAS phrase to both users console.log(challenge.value.shortAuthString); // => "amber falcon cedar lotus bridge crimson" // Both users scan QR code or read SAS aloud // If SAS matches, keys are verified const verified = verifyChallenge(challenge.value, userEnteredSas); if (verified.ok) { await tofu.upgradeTrust('alice@example.com', fingerprint); // => { trustLevel: 'verified', verifiedAt: ... } }
Transparency Log
Every key event is appended to a hash-chained transparency log. Each entry contains: contact email, public key fingerprint, action (registered/rotated/verified/revoked), timestamp, and a hash of the previous entry. Tampering with any entry breaks the hash chain. verifyChain() detects corruption by recomputing hashes and comparing to stored values.
// Append a key registration event await log.append('alice@example.com', fingerprint, 'key_registered'); // Verify hash chain integrity const valid = await log.verifyChain('alice@example.com'); if (!valid.ok) { console.error('Log corrupted!', valid.error); } // Get complete history for a contact const history = log.getHistory('alice@example.com'); // => [{ action: 'key_registered', timestamp: ..., previousEntryHash: ... }, ...]
Key Rotation
When a contact rotates their key, they must prove the new key is authorized by signing it with the old private key (RSA-PSS continuity proof). KeyRotationManager.initiateRotation() verifies the signature before accepting the new key. A grace period (default: 7 days) allows both old and new keys to decrypt messages during transition. After the grace period expires, only the new key is accepted.
// Contact generates new key pair and signs it with old key const newKeyPair = await generateSigningKeyPair(); const signature = await signContinuityProof(oldPrivateKey, newKeyPair.publicKey); // Rotation manager verifies and logs the rotation const rotationMgr = new KeyRotationManager(log, 7 * 24 * 60 * 60 * 1000); const result = await rotationMgr.initiateRotation( 'alice@example.com', oldPublicKey, newKeyPair.publicKey, signature ); // During grace period, both keys are accepted rotationMgr.isAccepted('alice@example.com', oldFingerprint); // true rotationMgr.isAccepted('alice@example.com', newFingerprint); // true // After grace period, only new key is accepted rotationMgr.isAccepted('alice@example.com', oldFingerprint); // false
Integration
Trust integrates with any messaging system that uses public key cryptography. It works alongside encryption libraries like @private.me/crypto and transport layers like @private.me/agent-sdk.
Install
pnpm add @private.me/trust
Quick Start
import { KeyTransparencyLog, TofuManager, computeFingerprint, createChallenge, verifyChallenge, } from '@private.me/trust'; // Initialize trust infrastructure const log = new KeyTransparencyLog(); const tofu = new TofuManager(log); // When receiving a message from a contact async function handleIncomingMessage(contact, publicKey, message) { const fingerprint = await computeFingerprint(publicKey); const trustResult = await tofu.processKey(contact, fingerprint); if (!trustResult.ok) { // Key changed unexpectedly — warn user if (trustResult.error.code === 'FINGERPRINT_CONFLICT') { const userConfirmed = await showWarningDialog(contact); if (userConfirmed) { await tofu.acceptKeyChange(contact, fingerprint); } else { return; // Reject message } } } // Key is trusted — decrypt and display message const plaintext = await decrypt(message, myPrivateKey); displayMessage(contact, plaintext); } // When meeting a contact in person for verification async function verifyContactInPerson(contact, theirKey) { const challenge = await createChallenge( myEmail, contact, myPublicKey, theirKey ); // Display QR code and SAS phrase showQRCode(challenge.value.qrData); showSasPhrase(challenge.value.shortAuthString); // User compares SAS with contact const userEnteredSas = await promptForSas(); const verified = verifyChallenge(challenge.value, userEnteredSas); if (verified.ok) { const fingerprint = await computeFingerprint(theirKey); await tofu.upgradeTrust(contact, fingerprint); showSuccessMessage('Contact verified!'); } else { showErrorMessage('Verification failed — possible MITM attack'); } }
Building Blocks Integration
Trust is designed to work with other PRIVATE.ME building blocks:
- @private.me/crypto — XorIDA threshold sharing, HMAC primitives
- @private.me/agent-sdk (Xlink) — DID-based M2M messaging with Trust for key verification
- @private.me/shared — Shared types (TrustLevel, KeyTransparencyEntry, etc.)
Security
Trust is designed to resist MITM attacks, detect log tampering, and prevent unauthorized key replacement.
Threat Model
| Attack Vector | Defense | Status |
|---|---|---|
| MITM on first key exchange | TOFU accepts first key, warns on changes | ✓ Mitigated |
| Silent key replacement | TOFU detects fingerprint changes, requires explicit user acceptance | ✓ Prevented |
| Forged verification SAS | SAS derived via HMAC-SHA256 of both fingerprints — unilateral forgery impossible | ✓ Prevented |
| Transparency log tampering | Hash chaining — any modification breaks the chain | ✓ Detectable |
| Unauthorized key rotation | RSA-PSS continuity proof — new key must be signed by old key | ✓ Prevented |
| Replay attacks during rotation | Rotation entries timestamped and logged — replays detectable | ✓ Detectable |
Cryptographic Primitives
All cryptographic operations use the Web Crypto API:
- SHA-256 — Key fingerprints (SPKI export digest), transparency log hashing
- HMAC-SHA256 — SAS derivation (prevents unilateral forgery)
- RSA-PSS 2048-bit — Continuity proofs for key rotation
- crypto.getRandomValues() — All random values (challenge IDs, nonces)
Security Properties
previousEntryHash field contains the SHA-256 digest of its predecessor. Modifying any historical entry invalidates all subsequent hashes. verifyChain() recomputes the entire chain to detect tampering.
Trust Levels
| Level | Meaning | How to Achieve |
|---|---|---|
| unknown | No key seen for this contact yet | Default state |
| tofu | Key accepted on first use, warns on changes | Automatic via processKey() |
| verified | Key confirmed via in-person verification ceremony | Manual via upgradeTrust() after SAS match |
Benchmarks
Trust operations are fast. Key fingerprint computation and transparency log operations complete in under 10ms on modern hardware.
Performance Characteristics
| Operation | Typical Time | Notes |
|---|---|---|
| computeFingerprint() | <1ms | SHA-256 of SPKI-exported public key |
| deriveSas() | <2ms | HMAC-SHA256 of two fingerprints |
| log.append() | <5ms | Hash previous entry + create new entry |
| log.verifyChain() | ~2ms per entry | Scales linearly with log size |
| signContinuityProof() | ~8ms | RSA-PSS 2048-bit signature |
| verifyContinuityProof() | ~1ms | RSA-PSS verification is faster than signing |
All benchmarks measured on Node.js 20 (MacBook Pro M1). Web Crypto API performance varies by browser and platform but is generally within 2x of these numbers.
Honest Limitations
Trust solves key verification but does not solve all security problems. Here's what it does NOT do.
TOFU is Vulnerable to Active MITM on First Exchange
If an attacker intercepts the very first key exchange, TOFU will accept the attacker's key. This is the fundamental trade-off of Trust-on-First-Use: ease of use vs. absolute security. The only way to eliminate this risk is to verify keys out-of-band (verification ceremony) before sending sensitive messages. For contacts you've never met in person, TOFU is the best you can do without a centralized PKI.
Transparency Log is Client-Side Only
The transparency log in this implementation is stored locally. It provides tamper detection but not tamper resistance — an attacker with filesystem access can delete the entire log. For server-backed transparency (like Certificate Transparency), you need a separate append-only ledger service. Trust provides the log structure but not the distribution mechanism.
No Automatic Key Revocation
If a contact's private key is compromised, they can revoke it (log entry: key_revoked), but there is no mechanism to automatically propagate revocation notices to all parties who trust that key. You need a separate revocation infrastructure (CRLs, OCSP, or blockchain-based revocation). Trust logs the revocation but does not distribute it.
Verification Ceremony Requires Out-of-Band Channel
The verification ceremony assumes both parties can compare SAS phrases via an out-of-band channel (in-person, phone call, video chat). If both parties are remote and have no trusted channel, the ceremony cannot be completed. This is a fundamental limitation of all key verification schemes that don't rely on centralized CAs.
Grace Period is a Policy Decision
The default 7-day grace period for key rotation is arbitrary. Organizations may need shorter (1 day for high-security environments) or longer (30 days for large distributed systems) periods. The KeyRotationManager accepts a configurable grace period, but choosing the right value is an operational decision, not a cryptographic one.
Full API Surface
Complete reference for all exported functions, classes, and types. Production-ready for integration.
Key Verification
Transparency Log
TOFU Manager
Key Rotation
Error Taxonomy
Trust uses structured errors with actionable hints. All errors follow the Result<T, E> pattern.
| Code | Description |
|---|---|
| VERIFICATION_EXPIRED | The verification challenge has exceeded its 15-minute TTL. Create a new challenge. |
| VERIFICATION_FAILED | The SAS phrase entered by the user does not match the expected value. |
| FINGERPRINT_CONFLICT | A different public key fingerprint is already registered for this contact. User must explicitly accept the key change. |
| CONTACT_NOT_FOUND | No trust record exists for the specified email address. |
| KEY_MISMATCH | The provided fingerprint does not match the contact's current key. |
| CONTINUITY_BROKEN | The RSA-PSS continuity proof for a key rotation is invalid. The new key was not signed by the old key. |
| LOG_CORRUPTED | The hash chain in the transparency log is broken. An entry's previousEntryHash does not match the SHA-256 of its predecessor. |
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/trust- 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