import { describe, test, expect } from 'bun:test'; import { createShade } from '../src/index.js'; import { createPrekeyServerWithKT, MemoryPrekeyStore, } from '@shade/server'; import { SubtleCryptoProvider } from '@shade/crypto-web'; import { MemoryKTLogStore } from '@shade/key-transparency'; const crypto = new SubtleCryptoProvider(); async function startServerWithKT() { const logKp = await crypto.generateEd25519KeyPair(); const { app, kt } = await createPrekeyServerWithKT({ crypto, store: new MemoryPrekeyStore(), disableRateLimit: true, keyTransparency: { store: new MemoryKTLogStore(), signingPrivateKey: logKp.privateKey, signingPublicKey: logKp.publicKey, }, }); const port = 22000 + Math.floor(Math.random() * 1000); const handle = Bun.serve({ port, fetch: app.fetch }); return { url: `http://localhost:${port}`, logKp, kt, stop: () => handle.stop() }; } describe('Shade.create() with keyTransparency config', () => { test('Alice and Bob exchange messages; SDK verifies KT proofs and observes STHs', async () => { const server = await startServerWithKT(); try { const bob = await createShade({ prekeyServer: server.url, address: 'bob', autoReplenish: false, keyTransparency: { mode: 'observe-strict', logPublicKey: server.logKp.publicKey, }, }); const alice = await createShade({ prekeyServer: server.url, address: 'alice', autoReplenish: false, keyTransparency: { mode: 'observe-strict', logPublicKey: server.logKp.publicKey, }, }); const env = await alice.send('bob', 'Hello with KT proof!'); const received = await bob.receive('alice', env); expect(received).toBe('Hello with KT proof!'); const witness = alice.getKTWitness(); expect(witness).not.toBeNull(); const latest = witness!.latestObserved(); expect(latest).not.toBeNull(); expect(latest!.treeSize).toBeGreaterThan(0); await alice.shutdown(); await bob.shutdown(); } finally { server.stop(); } }); test('observe-strict throws when server has KT off', async () => { const { createPrekeyServer, MemoryPrekeyStore } = await import('@shade/server'); const app = createPrekeyServer({ crypto, store: new MemoryPrekeyStore(), disableRateLimit: true, }); const port = 22800 + Math.floor(Math.random() * 200); const handle = Bun.serve({ port, fetch: app.fetch }); try { const bob = await createShade({ prekeyServer: `http://localhost:${port}`, address: 'bob', autoReplenish: false, }); const wrongKp = await crypto.generateEd25519KeyPair(); const alice = await createShade({ prekeyServer: `http://localhost:${port}`, address: 'alice', autoReplenish: false, keyTransparency: { mode: 'observe-strict', logPublicKey: wrongKp.publicKey, }, }); // Sending requires fetching Bob's bundle, which has no proof — strict mode fails await expect(alice.send('bob', 'hi')).rejects.toThrow(); await alice.shutdown(); await bob.shutdown(); } finally { handle.stop(); } }); test('rejects logPublicKey of wrong length', async () => { await expect( createShade({ prekeyServer: 'http://localhost:9999', address: 'x', autoReplenish: false, keyTransparency: { mode: 'observe', logPublicKey: new Uint8Array(31), }, }), ).rejects.toThrow(/32 bytes/); }); });