import type { CryptoProvider } from '@shade/core'; import { x25519 } from '@noble/curves/ed25519.js'; import { ed25519 } from '@noble/curves/ed25519.js'; /** * SubtleCrypto + noble/curves implementation of CryptoProvider. * * Uses @noble/curves for X25519 and Ed25519 (reliable across all runtimes) * and Web Crypto API for AES-256-GCM, HKDF, and HMAC (hardware-accelerated). */ export class SubtleCryptoProvider implements CryptoProvider { private readonly subtle: SubtleCrypto; constructor(subtle?: SubtleCrypto) { this.subtle = subtle ?? globalThis.crypto.subtle; } // ─── X25519 (via @noble/curves) ──────────────────────────── async generateX25519KeyPair(): Promise<{ publicKey: Uint8Array; privateKey: Uint8Array }> { const privateKey = this.randomBytes(32); const publicKey = x25519.getPublicKey(privateKey); return { publicKey, privateKey }; } async x25519(privateKey: Uint8Array, publicKey: Uint8Array): Promise { return x25519.getSharedSecret(privateKey, publicKey); } // ─── Ed25519 (via @noble/curves) ─────────────────────────── async generateEd25519KeyPair(): Promise<{ publicKey: Uint8Array; privateKey: Uint8Array }> { const privateKey = this.randomBytes(32); const publicKey = ed25519.getPublicKey(privateKey); return { publicKey, privateKey }; } async sign(privateKey: Uint8Array, message: Uint8Array): Promise { return ed25519.sign(message, privateKey); } async verify(publicKey: Uint8Array, message: Uint8Array, signature: Uint8Array): Promise { try { return ed25519.verify(signature, message, publicKey); } catch { return false; } } // ─── AES-256-GCM (via SubtleCrypto) ─────────────────────── async aesGcmEncrypt( key: Uint8Array, plaintext: Uint8Array, aad?: Uint8Array, ): Promise<{ ciphertext: Uint8Array; nonce: Uint8Array }> { const nonce = this.randomBytes(12); const aesKey = await this.subtle.importKey('raw', key, 'AES-GCM', false, ['encrypt']); const encrypted = await this.subtle.encrypt( { name: 'AES-GCM', iv: nonce, additionalData: aad }, aesKey, plaintext, ); return { ciphertext: new Uint8Array(encrypted), nonce }; } async aesGcmDecrypt( key: Uint8Array, ciphertext: Uint8Array, nonce: Uint8Array, aad?: Uint8Array, ): Promise { const aesKey = await this.subtle.importKey('raw', key, 'AES-GCM', false, ['decrypt']); const decrypted = await this.subtle.decrypt( { name: 'AES-GCM', iv: nonce, additionalData: aad }, aesKey, ciphertext, ); return new Uint8Array(decrypted); } // ─── Key Derivation (via SubtleCrypto) ───────────────────── async hkdf( ikm: Uint8Array, salt: Uint8Array, info: Uint8Array, length: number, ): Promise { const baseKey = await this.subtle.importKey('raw', ikm, 'HKDF', false, ['deriveBits']); const bits = await this.subtle.deriveBits( { name: 'HKDF', hash: 'SHA-256', salt, info }, baseKey, length * 8, ); return new Uint8Array(bits); } async hmacSha256(key: Uint8Array, data: Uint8Array): Promise { const hmacKey = await this.subtle.importKey( 'raw', key, { name: 'HMAC', hash: 'SHA-256' }, false, ['sign'], ); const sig = await this.subtle.sign('HMAC', hmacKey, data); return new Uint8Array(sig); } // ─── Random ──────────────────────────────────────────────── randomBytes(length: number): Uint8Array { const buf = new Uint8Array(length); globalThis.crypto.getRandomValues(buf); return buf; } // ─── Hardening ───────────────────────────────────────────── constantTimeEqual(a: Uint8Array, b: Uint8Array): boolean { if (a.length !== b.length) return false; let diff = 0; for (let i = 0; i < a.length; i++) { diff |= a[i]! ^ b[i]!; } return diff === 0; } zeroize(buf: Uint8Array): void { buf.fill(0); } randomUint32(): number { const buf = this.randomBytes(4); return new DataView(buf.buffer, buf.byteOffset, 4).getUint32(0, false); } }