import { randomBytes, sign as signMessageRaw, verify as verifySignatureRaw } from "node:crypto"; /** * Key pair for Yonexus client authentication * Uses Ed25519 for digital signatures */ export interface KeyPair { /** Base64-encoded Ed25519 private key */ readonly privateKey: string; /** Base64-encoded Ed25519 public key */ readonly publicKey: string; /** Algorithm identifier for compatibility */ readonly algorithm: "Ed25519"; } /** * Generate a new Ed25519 key pair for client authentication. * * In v1, we use Node.js crypto.generateKeyPairSync for Ed25519. * The keys are encoded as base64 for JSON serialization. */ export async function generateKeyPair(): Promise { const { generateKeyPair } = await import("node:crypto"); const { publicKey, privateKey } = await new Promise<{ publicKey: string; privateKey: string; }>((resolve, reject) => { generateKeyPair( "ed25519", { publicKeyEncoding: { type: "spki", format: "pem" }, privateKeyEncoding: { type: "pkcs8", format: "pem" } }, (err, pubKey, privKey) => { if (err) { reject(err); return; } resolve({ publicKey: pubKey, privateKey: privKey }); } ); }); return { privateKey, publicKey, algorithm: "Ed25519" }; } /** * Sign a message using the client's private key. * * @param privateKeyPem - PEM-encoded private key * @param message - Message to sign (Buffer or string) * @returns Base64-encoded signature */ export async function signMessage( privateKeyPem: string, message: Buffer | string ): Promise { const signature = signMessageRaw(null, typeof message === "string" ? Buffer.from(message) : message, privateKeyPem); return signature.toString("base64"); } /** * Verify a signature using the client's public key. * * @param publicKeyPem - PEM-encoded public key * @param message - Original message (Buffer or string) * @param signature - Base64-encoded signature * @returns Whether the signature is valid */ export async function verifySignature( publicKeyPem: string, message: Buffer | string, signature: string ): Promise { try { const sigBuffer = Buffer.from(signature, "base64"); return verifySignatureRaw(null, typeof message === "string" ? Buffer.from(message) : message, publicKeyPem, sigBuffer); } catch { return false; } } /** * Generate a cryptographically secure random pairing code. * Format: XXXX-XXXX-XXXX (12 alphanumeric characters in groups of 4) */ export function generatePairingCode(): string { const bytes = randomBytes(8); const chars = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789"; // Excludes confusing chars (0, O, 1, I) let code = ""; for (let i = 0; i < 12; i++) { code += chars[bytes[i % bytes.length] % chars.length]; } // Format as XXXX-XXXX-XXXX return `${code.slice(0, 4)}-${code.slice(4, 8)}-${code.slice(8, 12)}`; } /** * Generate a shared secret for client authentication. * This is issued by the server after successful pairing. */ export function generateSecret(): string { return randomBytes(32).toString("base64url"); } /** * Generate a 24-character nonce for authentication. */ export function generateNonce(): string { const bytes = randomBytes(18); return bytes.toString("base64url").slice(0, 24); }