import React, { useMemo, useState } from "react"; import { Globe, Copy, ExternalLink, CheckCircle2, AlertCircle, X, Eye, EyeOff, Info } from "lucide-react"; import { InteractionEntropy } from "../lib/interactionEntropy"; import { entropyToMnemonic } from "../lib/seedblend"; type RandomOrgStats = { source: "randomorg"; nRequested: number; nUsed: number; distribution: number[]; // counts for faces 1..6 interactionSamples: number; totalBits: number; }; interface RandomOrgEntropyProps { wordCount: 12 | 24; onEntropyGenerated: (mnemonic: string, stats: RandomOrgStats) => void; onCancel: () => void; interactionEntropy: InteractionEntropy; } function buildRequest(apiKey: string, n: number) { return { jsonrpc: "2.0", method: "generateIntegers", params: { apiKey, n, min: 1, max: 6, replacement: true, base: 10 }, id: 1, }; } function parseD6FromPaste(text: string): number[] { const t = text.trim(); if (!t) throw new Error("Paste the random.org response JSON (or an array) first."); // Allow direct array paste: [1,6,2,...] if (t.startsWith("[") && t.endsWith("]")) { const arr = JSON.parse(t); if (!Array.isArray(arr)) throw new Error("Expected an array."); return arr; } const obj = JSON.parse(t); const data = obj?.result?.random?.data; if (!Array.isArray(data)) throw new Error("Could not find result.random.data in pasted JSON."); return data; } async function mnemonicFromD6( d6: number[], wordCount: 12 | 24, interactionEntropy: InteractionEntropy ): Promise { const interactionBytes = await interactionEntropy.getEntropyBytes(); const cryptoBytes = crypto.getRandomValues(new Uint8Array(32)); // Keep it simple + consistent with your other sources: concatenate strings, SHA-256, slice entropy. const combined = [ d6.join(""), performance.now().toString(), Array.from(interactionBytes).join(","), Array.from(cryptoBytes).join(","), ].join("|"); const data = new TextEncoder().encode(combined); const hash = await crypto.subtle.digest("SHA-256", data); const entropyLength = wordCount === 12 ? 16 : 32; const finalEntropy = new Uint8Array(hash.slice(0, entropyLength)); return entropyToMnemonic(finalEntropy); } const RandomOrgEntropy: React.FC = ({ wordCount, onEntropyGenerated, onCancel, interactionEntropy, }) => { const [apiKey, setApiKey] = useState(""); const [showKey, setShowKey] = useState(false); const [n, setN] = useState(30); // min 30 const [paste, setPaste] = useState(""); const [error, setError] = useState(""); const [copied, setCopied] = useState(false); const [processing, setProcessing] = useState(false); const [stats, setStats] = useState(null); const [generatedMnemonic, setGeneratedMnemonic] = useState(""); const requestJson = useMemo(() => { const key = apiKey.trim() || "PASTE_YOUR_API_KEY_HERE"; return JSON.stringify(buildRequest(key, n), null, 2); }, [apiKey, n]); const copyRequest = async () => { setError(""); try { await navigator.clipboard.writeText(requestJson); setCopied(true); window.setTimeout(() => setCopied(false), 1200); } catch { setError("Clipboard write failed. Tap the JSON box to select all, then copy manually."); } }; const generate = async () => { setError(""); setProcessing(true); try { const raw = parseD6FromPaste(paste); if (raw.length < n) throw new Error(`Need at least ${n} D6 samples, got ${raw.length}.`); const d6 = raw.slice(0, n); const dist = [0, 0, 0, 0, 0, 0]; for (let i = 0; i < d6.length; i++) { const v = d6[i]; if (!Number.isInteger(v) || v < 1 || v > 6) throw new Error(`Invalid D6 at index ${i}: ${String(v)}`); dist[v - 1]++; } const mnemonic = await mnemonicFromD6(d6, wordCount, interactionEntropy); setGeneratedMnemonic(mnemonic); setStats({ source: "randomorg", nRequested: n, nUsed: d6.length, distribution: dist, interactionSamples: interactionEntropy.getSampleCount().total, totalBits: 256, }); } catch (e) { setStats(null); setGeneratedMnemonic(""); setError(e instanceof Error ? e.message : "Failed."); } finally { setProcessing(false); } }; return (
{!stats && !processing && ( <>

🌍 Random.org Entropy

SeedPGP will not contact random.org. You run the request in another tab/tool and paste the response here.

setApiKey(e.target.value)} placeholder="Paste API key (optional; not stored)" className="w-full pl-3 pr-10 py-2 bg-[#16213e] border-2 border-[#00f0ff]/50 rounded-lg text-[#00f0ff] text-xs placeholder-[#9d84b7] focus:outline-none focus:border-[#ff006e] transition-all" autoCapitalize="none" autoCorrect="off" spellCheck={false} />
{n}
setN(parseInt(e.target.value, 10))} className="w-full accent-[#ff006e]" />

Minimum 30. Step 10.

Request Builder