Files
Shade/examples/02-prekey-server/main.ts
Sterister 75008b623a
Some checks failed
Test / test (push) Has been cancelled
docs: M-Hard 9-11 — README, examples, CI, benchmarks, migration guide
M-Hard 9: Documentation + examples
- README.md, SECURITY.md, THREAT-MODEL.md
- 5 runnable examples: basic conversation, prekey server,
  WebSocket tunnel, identity verification, Dokploy deployment

M-Hard 10: CI + publishing + benchmarks
- GitHub Actions: test workflow with PostgreSQL service container
- GitHub Actions: publish workflow for npm releases on git tags
- Benchmark suite (bench/run.ts) with markdown output
- LICENSE (MIT), CHANGELOG.md, CONTRIBUTING.md

M-Hard 11: Migration guide
- MIGRATION.md with three-phase rollout strategy
- Concrete examples for replacing static AES tunnels
- Concrete examples for per-device push notification migration
- Sections for Orchestrator and Nova migrations

Benchmark highlights:
- AES-256-GCM: ~100K ops/sec
- Encrypt+decrypt roundtrip: ~17K ops/sec
- X3DH handshake: ~165 ops/sec (hardware acceleration limited)
- Compute fingerprint: ~76K ops/sec

All 11 M-Hard milestones complete. 193 tests passing, 0 failures.
Shade is production-ready.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 17:58:30 +02:00

79 lines
3.3 KiB
TypeScript

import { ShadeSessionManager } from '../../packages/shade-core/src/index.js';
import { SubtleCryptoProvider, MemoryStorage } from '../../packages/shade-crypto-web/src/index.js';
import { createPrekeyServer, MemoryPrekeyStore } from '../../packages/shade-server/src/index.js';
import { ShadeFetchTransport } from '../../packages/shade-transport/src/index.js';
const crypto = new SubtleCryptoProvider();
async function main() {
console.log('=== Shade Prekey Server Demo ===\n');
// Start the prekey server
const store = new MemoryPrekeyStore();
const server = createPrekeyServer({ crypto, store });
const port = 19850;
const handle = Bun.serve({ port, fetch: server.fetch });
console.log(`Prekey server listening on http://localhost:${port}\n`);
try {
// ─── Bob registers ────────────────────────────────────
const bobMgr = new ShadeSessionManager(crypto, new MemoryStorage());
await bobMgr.initialize();
const bobIdentity = await new MemoryStorage().getIdentityKeyPair();
// Note: in real usage, get the storage instance you passed in. Simplified here:
const bobStorageRef = new MemoryStorage();
const bobMgr2 = new ShadeSessionManager(crypto, bobStorageRef);
await bobMgr2.initialize();
const bobKp = await bobStorageRef.getIdentityKeyPair();
const bobTransport = new ShadeFetchTransport({
baseUrl: `http://localhost:${port}`,
crypto,
signingPrivateKey: bobKp!.signingPrivateKey,
});
const bobOTPKs = await bobMgr2.generateOneTimePreKeys(10);
const bobBundle = await bobMgr2.createPreKeyBundle();
await bobTransport.register('bob', bobMgr2.getPublicIdentity(), bobBundle.signedPreKey, bobOTPKs);
console.log('Bob registered with prekey server (10 one-time prekeys uploaded)');
console.log('Bob fingerprint:', await bobMgr2.getIdentityFingerprint());
console.log();
// ─── Alice fetches Bob's bundle anonymously ───────────
const aliceMgr = new ShadeSessionManager(crypto, new MemoryStorage());
await aliceMgr.initialize();
console.log('Alice fingerprint:', await aliceMgr.getIdentityFingerprint());
// Alice doesn't need a signing key to fetch
const aliceTransport = new ShadeFetchTransport({
baseUrl: `http://localhost:${port}`,
crypto,
});
const fetchedBundle = await aliceTransport.fetchBundle('bob');
console.log('\nAlice fetched Bob\'s bundle');
console.log('Remaining one-time prekeys:', await aliceTransport.getKeyCount('bob'));
await aliceMgr.initSessionFromBundle('bob', fetchedBundle);
// ─── Encrypted conversation ───────────────────────────
console.log('\n=== Encrypted conversation ===');
const env1 = await aliceMgr.encrypt('bob', 'Hello via prekey server!');
console.log('→ Alice sends encrypted message');
const plain1 = await bobMgr2.decrypt('alice', env1);
console.log(`← Bob decrypts: "${plain1}"`);
const env2 = await bobMgr2.encrypt('alice', 'Got your message!');
console.log('\n→ Bob replies (DH ratchet step)');
const plain2 = await aliceMgr.decrypt('bob', env2);
console.log(`← Alice decrypts: "${plain2}"`);
console.log('\n=== Done ===');
} finally {
handle.stop();
}
}
main().catch(console.error);