/** * Collects entropy from user interactions (mouse, keyboard, touch) * Runs in background to enhance any entropy generation method */ export class InteractionEntropy { private samples: number[] = []; private lastEvent = 0; private startTime = performance.now(); private sources = { mouse: 0, keyboard: 0, touch: 0 }; constructor() { this.initListeners(); } private initListeners() { const handleEvent = (e: MouseEvent | KeyboardEvent | TouchEvent) => { const now = performance.now(); const delta = now - this.lastEvent; if (delta > 0 && delta < 10000) { // Ignore huge gaps this.samples.push(delta); if (e instanceof MouseEvent) { this.samples.push(e.clientX ^ e.clientY); this.sources.mouse++; } else if (e instanceof KeyboardEvent) { this.samples.push(e.key.codePointAt(0) ?? 0); this.sources.keyboard++; } else if (e instanceof TouchEvent && e.touches[0]) { this.samples.push(e.touches[0].clientX ^ e.touches[0].clientY); this.sources.touch++; } } this.lastEvent = now; // Keep last 256 samples (128 pairs) if (this.samples.length > 256) { this.samples.splice(0, this.samples.length - 256); } }; document.addEventListener('mousemove', handleEvent); document.addEventListener('keydown', handleEvent); document.addEventListener('touchmove', handleEvent); } async getEntropyBytes(): Promise { // Convert samples to entropy via SHA-256 const data = new TextEncoder().encode( this.samples.join(',') + performance.now() ); const hash = await crypto.subtle.digest('SHA-256', data); return new Uint8Array(hash); } getSampleCount(): { mouse: number; keyboard: number; touch: number; total: number } { return { ...this.sources, total: this.sources.mouse + this.sources.keyboard + this.sources.touch }; } getCollectionTime(): number { return performance.now() - this.startTime; } clear() { this.samples = []; this.lastEvent = 0; this.startTime = performance.now(); this.sources = { mouse: 0, keyboard: 0, touch: 0 }; } }