function getCryptoApi() { const api = globalThis.crypto; if (!api?.subtle || typeof api.getRandomValues !== 'function') { throw new Error('WebCrypto недоступен в текущем браузере.'); } return api; } function getSubtleApi() { return getCryptoApi().subtle; } function base64UrlToBase64(value) { const normalized = String(value || '').trim().replace(/-/g, '+').replace(/_/g, '/'); return normalized + '='.repeat((4 - (normalized.length % 4)) % 4); } export function utf8Bytes(value) { return new TextEncoder().encode(String(value ?? '')); } export function bytesToBase64(bytes) { let binary = ''; const chunk = 0x8000; for (let i = 0; i < bytes.length; i += chunk) { const slice = bytes.subarray(i, i + chunk); binary += String.fromCharCode(...slice); } return btoa(binary); } export function base64ToBytes(value) { const binary = atob(base64UrlToBase64(value)); const out = new Uint8Array(binary.length); for (let i = 0; i < binary.length; i += 1) out[i] = binary.charCodeAt(i); return out; } export async function generateEd25519Pair() { return getSubtleApi().generateKey({ name: 'Ed25519' }, true, ['sign', 'verify']); } export async function exportEd25519PublicKeyB64(publicKey) { const raw = await getSubtleApi().exportKey('raw', publicKey); return bytesToBase64(new Uint8Array(raw)); } export async function exportPkcs8B64(privateKey) { const raw = await getSubtleApi().exportKey('pkcs8', privateKey); return bytesToBase64(new Uint8Array(raw)); } export async function importPkcs8Ed25519(pkcs8B64) { return getSubtleApi().importKey('pkcs8', base64ToBytes(pkcs8B64), { name: 'Ed25519' }, false, ['sign']); } export async function signBase64(privateKey, text) { const signature = await getSubtleApi().sign({ name: 'Ed25519' }, privateKey, utf8Bytes(text)); return bytesToBase64(new Uint8Array(signature)); } export async function sha256Bytes(bytes) { const digest = await getSubtleApi().digest('SHA-256', bytes); return new Uint8Array(digest); } export async function sha256Text(text) { return sha256Bytes(utf8Bytes(text)); } export function randomBase64(size) { const bytes = getCryptoApi().getRandomValues(new Uint8Array(size)); return bytesToBase64(bytes); } export function bytesToHex(bytes) { return [...bytes].map((byte) => byte.toString(16).padStart(2, '0')).join(''); }