import React, { useState } from 'react'; import { Dices, CheckCircle2, AlertCircle, X } from 'lucide-react'; import { InteractionEntropy } from '../lib/interactionEntropy'; interface DiceStats { rolls: string; length: number; distribution: number[]; chiSquare: number; passed: boolean; interactionSamples: number; } interface DiceEntropyProps { wordCount: 12 | 24; onEntropyGenerated: (mnemonic: string, stats: any) => void; onCancel: () => void; interactionEntropy: InteractionEntropy; } const DiceEntropy: React.FC = ({ wordCount, onEntropyGenerated, onCancel, interactionEntropy }) => { const [rolls, setRolls] = useState(''); const [error, setError] = useState(''); const [processing, setProcessing] = useState(false); const [stats, setStats] = useState(null); const [generatedMnemonic, setGeneratedMnemonic] = useState(''); const validateDiceRolls = (input: string): { valid: boolean; error: string } => { const clean = input.replace(/\s/g, ''); if (clean.length < 99) { return { valid: false, error: `Need at least 99 dice rolls (currently ${clean.length})` }; } if (/(\d)\1{6,}/.test(clean)) { return { valid: false, error: 'Too many repeated digits - roll again' }; } if (/(\d)(\d)\1\2\1\2\1\2/.test(clean)) { return { valid: false, error: 'Repeating pattern detected - roll again' }; } if (/(?:123456|654321)/.test(clean)) { return { valid: false, error: 'Sequential pattern detected - roll again' }; } const counts = Array(6).fill(0); for (const char of clean) { const digit = parseInt(char, 10); if (digit >= 1 && digit <= 6) counts[digit - 1]++; } const expected = clean.length / 6; const threshold = expected * 0.4; // Allow 40% deviation for (let i = 0; i < 6; i++) { if (Math.abs(counts[i] - expected) > threshold) { return { valid: false, error: `Poor distribution: digit ${i + 1} appears ${counts[i]} times (expected ~${Math.round(expected)})` }; } } const chiSquare = counts.reduce((sum, count) => { const diff = count - expected; return sum + (diff * diff) / expected; }, 0); if (chiSquare > 15.5) { // p-value < 0.01 for 5 degrees of freedom return { valid: false, error: `Statistical test failed (χ²=${chiSquare.toFixed(2)}) - rolls too predictable` }; } return { valid: true, error: '' }; }; const handleGenerate = async () => { const validation = validateDiceRolls(rolls); if (!validation.valid) { setError(validation.error); return; } setError(''); setProcessing(true); const clean = rolls.replace(/\s/g, ''); // Calculate stats const counts = Array(6).fill(0); for (const char of clean) { const digit = parseInt(char); if (digit >= 1 && digit <= 6) counts[digit - 1]++; } const expected = clean.length / 6; const chiSquare = counts.reduce((sum, count) => { const diff = count - expected; return sum + (diff * diff) / expected; }, 0); const diceStats: DiceStats = { rolls: clean, length: clean.length, distribution: counts, chiSquare, passed: true, interactionSamples: interactionEntropy.getSampleCount().total, }; // Generate mnemonic const mnemonic = await generateMnemonicFromDice(clean); // Show stats FIRST setStats(diceStats); setGeneratedMnemonic(mnemonic); // Store mnemonic for later setProcessing(false); // DON'T call onEntropyGenerated yet - let user review stats first }; const generateMnemonicFromDice = async (diceRolls: string): Promise => { const interactionBytes = await interactionEntropy.getEntropyBytes(); const cryptoBytes = crypto.getRandomValues(new Uint8Array(32)); const sources = [ diceRolls, performance.now().toString(), Array.from(interactionBytes).join(','), Array.from(cryptoBytes).join(',') ]; const combined = sources.join('|'); const data = new TextEncoder().encode(combined); const hash = await crypto.subtle.digest('SHA-256', data); const { entropyToMnemonic } = await import('bip39'); const entropyLength = wordCount === 12 ? 16 : 32; const finalEntropy = new Uint8Array(hash).slice(0, entropyLength); const entropyHex = Buffer.from(finalEntropy).toString('hex'); return entropyToMnemonic(entropyHex); }; return (
{/* INPUT FORM - Show only when stats are NOT shown */} {!stats && !processing && ( <>

Dice Roll Entropy

Instructions:

  • Roll a 6-sided die at least 99 times
  • Enter each result (1-6) in order
  • No spaces needed (e.g., 163452...)
  • Pattern validation enabled