/** * Shared test fixtures: Ed25519 key setup + in-memory port fakes (P3/P1 supply real impls). */ import { randomUUID } from 'node:crypto' import type { HostRecord } from 'relay-contracts' import { configureVerifyKey, resetVerifyKeyForTest, } from '../src/config/keys.js' import { generateEd25519KeyPair, exportEd25519PublicRaw } from '../src/crypto/ed25519.js' import type { AuthenticatedPrincipal, AuditEvent, AuditSink, HostRegistryPort, SessionRegistryPort, RevocationStore, TokenBucketStore, } from '../src/types.js' export async function setupP5SigningKey(): Promise<{ signingKey: CryptoKey; publicRaw: Uint8Array }> { resetVerifyKeyForTest() const pair = await generateEd25519KeyPair() await configureVerifyKey(pair.publicKey) const publicRaw = await exportEd25519PublicRaw(pair.publicKey) return { signingKey: pair.privateKey, publicRaw } } export async function makeEphemeral(): Promise<{ privateKey: CryptoKey; publicRaw: Uint8Array }> { const pair = await generateEd25519KeyPair() return { privateKey: pair.privateKey, publicRaw: await exportEd25519PublicRaw(pair.publicKey) } } export function principal(accountId: string, overrides: Partial = {}): AuthenticatedPrincipal { return { kind: 'human', accountId, principalId: 'cred-' + accountId, amr: ['passkey'], authAt: 1000, stepUpAt: null, ...overrides, } } export function makeHost(accountId: string, hostId: string, status: HostRecord['status'] = 'online'): HostRecord { return { hostId, accountId, subdomain: 'sub-' + hostId, agentPubkey: new Uint8Array(32), enrollFpr: 'fpr-' + hostId, status, lastSeen: '2026-01-01T00:00:00.000Z', createdAt: '2026-01-01T00:00:00.000Z', revokedAt: null, } } export const uuid = (): string => randomUUID() export function fakeHostRegistry(hosts: readonly HostRecord[]): HostRegistryPort { const map = new Map(hosts.map((h) => [h.hostId, h])) return { getById: async (id) => map.get(id) ?? null } } export function fakeSessionRegistry( sessions: readonly { sessionId: string; hostId: string; accountId: string }[], ): SessionRegistryPort { const map = new Map(sessions.map((s) => [s.sessionId, { hostId: s.hostId, accountId: s.accountId }])) return { getById: async (id) => map.get(id) ?? null } } export function fakeRevocationStore(): RevocationStore & { revoked: Set; consumed: Set } { const revoked = new Set() const consumed = new Set() return { revoked, consumed, isRevoked: async (jti) => revoked.has(jti), revokeJti: async (jti) => { revoked.add(jti) }, consumeOnce: async (jti) => { if (consumed.has(jti)) return false consumed.add(jti) return true }, } } /** Token bucket that always allows unless a key is added to `blocked`. */ export function fakeTokenBucket(): TokenBucketStore & { blocked: Set; calls: string[] } { const blocked = new Set() const calls: string[] = [] return { blocked, calls, take: async (key) => { calls.push(key) return !blocked.has(key) }, } } export function fakeAuditSink(): AuditSink & { events: AuditEvent[] } { const events: AuditEvent[] = [] return { events, append: async (e) => void events.push(e) } } /** True token bucket: per-key state initialized on first take, refilled by elapsed wall-time. */ export function fakeCountingBucket(): TokenBucketStore { const state = new Map() return { take: async (key, refillPerSec, burst, now) => { let s = state.get(key) if (s === undefined) { s = { tokens: burst, last: now, burst, refill: refillPerSec } state.set(key, s) } const elapsed = Math.max(0, now - s.last) s.tokens = Math.min(s.burst, s.tokens + elapsed * s.refill) s.last = now if (s.tokens < 1) return false s.tokens -= 1 return true }, } } // ── Token + matching DPoP proof (for authz/enforce/tripwire tests) ────────────────────────────── import { issueCapabilityToken } from '../src/capability/issue.js' import { buildDpopProof, type DpopContext } from '../src/capability/verify.js' import { jwkThumbprint } from '../src/crypto/thumbprint.js' import type { CapabilityRight } from 'relay-contracts' export interface IssuedBundle { readonly raw: string readonly dpop: DpopContext } export async function issueWithDpop( signingKey: CryptoKey, opts: { accountId: string host: string aud: string rights?: readonly CapabilityRight[] ttl?: number now: number htu?: string htm?: string }, ): Promise { const eph = await makeEphemeral() const cnfJkt = await jwkThumbprint(eph.publicRaw) const raw = await issueCapabilityToken( { principal: principal(opts.accountId), aud: opts.aud, host: opts.host, rights: opts.rights ?? ['attach'], ttlSeconds: opts.ttl ?? 45, cnfJkt, }, signingKey, opts.now, ) const htu = opts.htu ?? `https://${opts.aud}/ws` const htm = opts.htm ?? 'GET' const proofJws = await buildDpopProof(eph.privateKey, eph.publicRaw, { htu, htm, jti: uuid(), iat: opts.now, }) return { raw, dpop: { proofJws, htu, htm } } }