/** * x402 CRDT payment scheme — server-side verifier. * * Verifies CRDT payment payloads: JWT signature, DID match, * timestamp freshness, and nonce replay protection. */ // Lazy-import to avoid pulling EncryptID SDK at module level in shared/ let _verifyToken: ((token: string) => Promise<{ sub: string; did?: string; username?: string }>) | null = null; export function setTokenVerifier(fn: (token: string) => Promise<{ sub: string; did?: string; username?: string }>) { _verifyToken = fn; } export interface CrdtPaymentPayload { scheme: "crdt"; jwtToken: string; fromDid: string; fromLabel?: string; amount: number; tokenId: string; nonce: string; timestamp: number; } export interface CrdtVerifyResult { valid: boolean; error?: string; fromDid?: string; fromLabel?: string; } // In-memory nonce store with TTL (sufficient for single-instance deployment) const usedNonces = new Map(); const NONCE_TTL_MS = 10 * 60 * 1000; // 10 minutes const TIMESTAMP_WINDOW_MS = 5 * 60 * 1000; // 5 minutes // Cleanup expired nonces every 5 minutes setInterval(() => { const now = Date.now(); for (const [nonce, ts] of usedNonces) { if (now - ts > NONCE_TTL_MS) usedNonces.delete(nonce); } }, 5 * 60 * 1000).unref?.(); /** * Verify a CRDT payment payload. * Does NOT check balance or execute transfer — that's the middleware's job. */ export async function verifyCrdtPayment( payload: CrdtPaymentPayload, requiredAmount: number, requiredTokenId: string, ): Promise { // 1. Basic field validation if (!payload.jwtToken || !payload.fromDid || !payload.nonce || !payload.timestamp) { return { valid: false, error: "Missing required fields" }; } if (payload.tokenId !== requiredTokenId) { return { valid: false, error: `Wrong token: expected ${requiredTokenId}, got ${payload.tokenId}` }; } if (payload.amount < requiredAmount) { return { valid: false, error: `Insufficient amount: need ${requiredAmount}, got ${payload.amount}` }; } // 2. Timestamp freshness const age = Math.abs(Date.now() - payload.timestamp); if (age > TIMESTAMP_WINDOW_MS) { return { valid: false, error: "Payment timestamp too old or too far in future" }; } // 3. Nonce replay protection if (usedNonces.has(payload.nonce)) { return { valid: false, error: "Nonce already used (replay detected)" }; } // 4. JWT verification if (!_verifyToken) { return { valid: false, error: "Token verifier not configured" }; } try { const claims = await _verifyToken(payload.jwtToken); const claimsDid = (claims.did as string) || `did:key:${(claims.sub as string).slice(0, 32)}`; if (claimsDid !== payload.fromDid && claims.sub !== payload.fromDid) { return { valid: false, error: "JWT DID does not match payment fromDid" }; } // All checks passed — mark nonce as used usedNonces.set(payload.nonce, Date.now()); return { valid: true, fromDid: claimsDid, fromLabel: payload.fromLabel || claims.username || claimsDid, }; } catch (e) { return { valid: false, error: `JWT verification failed: ${(e as Error).message}` }; } }