// ============================================================================ // Entropy Edge Resolver — Reference Implementation // ============================================================================ // This JS mirrors the Rust `best_edge()` in rana's src/entropy.rs exactly. // Any Nostr client can use this to determine which prefix/suffix edge of an // npub to display prominently. The npub is self-describing. // ============================================================================ const BECH32_PREFIX = 'npub1'; const BECH32_MAX_ENTROPY = 5.0; // log2(32) — the bech32 alphabet has 32 symbols const ENTROPY_FLOOR = 3.0; // log2(8) — only edges with ≤8 distinct symbols qualify /// Shannon entropy in bits/char. function shannonEntropy(str) { if (!str || str.length === 0) return 0.0; const counts = {}; for (const ch of str) counts[ch] = (counts[ch] || 0) + 1; const n = str.length; let h = 0; for (const ch in counts) { const p = counts[ch] / n; h -= p * Math.log2(p); } return h; } /// Difficulty metric: L × (5 − H) — "bits of pattern". function edgeDifficulty(str) { if (!str || str.length === 0) return 0.0; const h = shannonEntropy(str); if (h > ENTROPY_FLOOR) return 0.0; // edge is too random — no pattern credit return str.length * (BECH32_MAX_ENTROPY - h); } /// Resolve the best edge: scan prefix and suffix lengths 1..29, /// return the one with max difficulty. function bestEdge(npub) { const data = npub.startsWith(BECH32_PREFIX) ? npub.slice(BECH32_PREFIX.length) : npub; const dataLen = data.length; if (dataLen === 0) return { side: 'prefix', length: 0, entropy: 0, difficulty: 0 }; const maxEdge = Math.min(29, dataLen); let best = { side: 'prefix', length: 0, entropy: 0, difficulty: 0 }; for (let L = 1; L <= maxEdge; L++) { const edge = data.slice(0, L); const h = shannonEntropy(edge); if (h > ENTROPY_FLOOR) continue; // skip edges above the quality floor const diff = L * (BECH32_MAX_ENTROPY - h); if (diff > best.difficulty) best = { side: 'prefix', length: L, entropy: h, difficulty: diff }; } for (let L = 1; L <= maxEdge; L++) { const edge = data.slice(dataLen - L); const h = shannonEntropy(edge); if (h > ENTROPY_FLOOR) continue; // skip edges above the quality floor const diff = L * (BECH32_MAX_ENTROPY - h); if (diff > best.difficulty) best = { side: 'suffix', length: L, entropy: h, difficulty: diff }; } return best; } /// Compute all prefix and suffix difficulties for the bar chart. function allEdgeDifficulties(npub) { const data = npub.startsWith(BECH32_PREFIX) ? npub.slice(BECH32_PREFIX.length) : npub; const dataLen = data.length; const maxEdge = Math.min(29, dataLen); const prefix = []; const suffix = []; for (let L = 1; L <= maxEdge; L++) { prefix.push({ length: L, entropy: shannonEntropy(data.slice(0, L)), difficulty: edgeDifficulty(data.slice(0, L)), }); suffix.push({ length: L, entropy: shannonEntropy(data.slice(dataLen - L)), difficulty: edgeDifficulty(data.slice(dataLen - L)), }); } return { prefix, suffix }; } // ============================================================================ // Rendering // ============================================================================ /// Render an npub with the winning edge highlighted. function renderNpubHTML(npub, edge) { const data = npub.startsWith(BECH32_PREFIX) ? npub.slice(BECH32_PREFIX.length) : npub; const dataLen = data.length; const edgeLen = Math.min(edge.length, dataLen); if (edge.side === 'prefix') { const e = data.slice(0, edgeLen); const rest = data.slice(edgeLen); return `${BECH32_PREFIX}${e}${rest}`; } else { const split = dataLen - edgeLen; const rest = data.slice(0, split); const e = data.slice(split); return `${BECH32_PREFIX}${rest}${e}`; } } /// Render the interactive resolver result. function renderResolver(npub) { const edge = bestEdge(npub); const allDiff = allEdgeDifficulties(npub); // Npub display document.getElementById('resolver-npub').innerHTML = renderNpubHTML(npub, edge); // Stats const sideStr = edge.side; document.getElementById('resolver-stats').innerHTML = ` Best edge: ${sideStr}, ${edge.length} chars — entropy ${edge.entropy.toFixed(3)} bits/char — difficulty ${edge.difficulty.toFixed(1)} bits `; // Bar chart renderBarChart(allDiff, edge); } /// Render the difficulty bar chart. function renderBarChart(allDiff, bestEdge) { const maxDiff = Math.max( ...allDiff.prefix.map((d) => d.difficulty), ...allDiff.suffix.map((d) => d.difficulty), 1 ); let html = '